2.4.7 softirq incorrectness.
This current code is bogus. Consider:
spin_lock_irqsave(flags); cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); spin_unlock_irqrestore(flags);
Oops... softirq not run until the next interrupt. So, EITHER:
1) make local_irq_restore check for pending softirqs in as we do for local_bh_enable(), and get rid of the wakeup_softirqd() in cpu_raise_softirq(). ie. all "exits" from in_interrupt == true are symmetrical.
*OR*
2) Change the check in cpu_raise_softirq to:
if (!in_hw_irq_handler(cpu)) wakeup_softirqd(cpu);
and implement in_hw_irq_handler() on all platforms. Then get rid of the test in local_bh_enable().
Please pick one approach or the other, and stick with it! The current code does half of each, badly. 8(
Thanks,
Rusty.
--
Premature optmztion is rt of all evl. --DK
Quoted 4 lines · Rusty Russell
This current code is bogus. Consider:
spin_lock_irqsave(flags); cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); spin_unlock_irqrestore(flags);
What kernel are you looking at? There's no such code in 2.4.7, the only two raise of the NET_RX_SOFTIRQ softirq are in dev.c in net_rx_action and netif_rx:
here the one in netif_rx:
__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); local_irq_restore(flags);
here the one in net_rx_action:
/* This already runs in BH context, no need to wake up BH's */ __cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); local_irq_enable();
Quoted 1 line
Oops... softirq not run until the next interrupt. So, EITHER:
The first netif_rx is required to run from interrupt handler (otherwise we should have executed cpu_raise_softirq and not __cpu_raise_softirq) so we cannot miss the do_softirq in the return path from do_IRQ() and so we cannot wait for the next incoming interrupt (if we have a overflow of the do_softirq loop ksoftirqd will take care of it without waiting for the next interrupt as it could instead happen in old 2.4 kernels).
The second net_rx_action is running into the softirq code itself that will marks itself runnable again and this will generate a do_softirq overflow that is handled gracefully by ksoftirqd again without waiting for the next interrupt (in old 2.4 kernels you had to wait for the next irq instead).
I cannot see any problem.
Andrea
Quoted 4 lines · Andrea Arcangeli
here the one in
netif_rx:__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); local_irq_restore(flags);
Quoted 8 lines
[...]
The first
netif_rxis required to run from interrupt handler (otherwise we should have executedcpu_raise_softirqand not__cpu_raise_softirq) so we cannot miss thedo_softirqin the return path fromdo_IRQ()and so we cannot wait for the next incoming interrupt (if we have a overflow of thedo_softirqloop ksoftirqd will take care of it without waiting for the next interrupt as it could instead happen in old 2.4 kernels).
Hmmh, wait a second. I take it that means calling netif_rx not from hard-irq context, but e.g. from bh is a bug? (Even if the only consequence is delaying the processing by up to one timer tick?)
If so, I believe this bug exists in a couple of a places. One example is - of course - the ISDN code, where netif_rx() is called from bh context. But I would think that e.g. ppp_generic is affected as well.
Could you clarify this? Maybe adding something like the following to netif_rx() is a good idea?
if (!in_irq) printk(KERN_WARNING "netif_rx called not in_irq() from %p\n", __builtin_return_address(0));
--Kai
Quoted 16 lines · Kai Germaschewski
Quoted 4 lines · Andrea Arcangeli
here the one in
netif_rx:__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); local_irq_restore(flags);Quoted 8 lines
[...]
The first
netif_rxis required to run from interrupt handler (otherwise we should have executedcpu_raise_softirqand not__cpu_raise_softirq) so we cannot miss thedo_softirqin the return path fromdo_IRQ()and so we cannot wait for the next incoming interrupt (if we have a overflow of thedo_softirqloop ksoftirqd will take care of it without waiting for the next interrupt as it could instead happen in old 2.4 kernels).
Quoted 7 lines
Hmmh, wait a second. I take it that means calling
netif_rxnot from hard-irq context, but e.g. from bh is a bug? (Even if the only consequence is delaying the processing by up to one timer tick?)If so, I believe this bug exists in a couple of a places. One example is - of course - the ISDN code, where
netif_rx()is called from bh context. But I would think that e.g.ppp_genericis affected as well.
no, as long as you mark the bh (or local_enable/disable_bh) you will run the softirqs.
Signature · 3 lines
Jeff Garzik | "I wouldn't be so judgemental Building 1024 | if you weren't such a sick freak." MandrakeSoft | -- goats.com
In message <[email protected]> you write:
Quoted 7 lines · Rusty Russell
Quoted 4 lines
This current code is bogus. Consider:
spin_lock_irqsave(flags); cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); spin_unlock_irqrestore(flags);What kernel are you looking at? There's no such code in 2.4.7, the only
Oh, so it's only a trap *waiting* to happen. That's OK then!
Quoted 3 lines
The first
netif_rxis required to run from interrupt handler (otherwise we should have executedcpu_raise_softirqand not__cpu_raise_softirq)
Aside: why does it do a local_irq_save() if it's always run from an interrupt handler?
Quoted 1 line
I cannot see any problem.
Why not fix all the cases? Why have this wierd secret rule that cpu_raise_softirq() should not be called with irqs disabled?
Call me old-fashioned, but why not *fix* the problem, if you're going to rewrite this code... again...
Rusty.
--
Premature optmztion is rt of all evl. --DK
Quoted 4 lines · Rusty Russell
In message <[email protected]> you write:
Quoted 1 line
What kernel are you looking at? There's no such code in 2.4.7, the only
Oh, so it's only a trap *waiting* to happen. That's OK then!
...
Quoted 2 lines
Why not fix all the cases? Why have this wierd secret rule that
cpu_raise_softirq()should not be called with irqs disabled?
Why keep it secret? I think Andrea is exactly right here, and we should just comment this restriction. That's all.
Later,
David S. Miller
[email protected]
Quoted 2 lines · Kai Germaschewski
Hmmh, wait a second. I take it that means calling
netif_rxnot from hard-irq context, but e.g. from bh is a bug? (Even if the only consequence
No it isn't a bug either, calling it from irq or bh is perfectly fine w.r.t. softirq latency. If you post it from softirq ksoftirqd will handle the overflow event exactly like in net_rx_action.
Andrea
Quoted 2 lines · Rusty Russell
Aside: why does it do a
local_irq_save()if it's always run from an interrupt handler?
to avoid corrupting the backlog with nested irqs.
However if you want a microoptimization is to sti before __cpu_raise_softirq, __cpu_raise_softirq from 2.4.7 is required to be atomic with respect of irqs (but it doesn't need to be atomic with respect of SMP). in the x86 port is handled as a single not locked bts instruction. So it can be run with irq enabled.
Here the optimization:
net/core/dev.c renamed from net/core/dev.c.~1~ +2 -2
@@ -1217,10 +1217,10 @@ enqueue:dev_hold (skb->dev);__skb_queue_tail (&queue->input_pkt_queue,skb); +local_irq_restore (flags);/* Runs from irqs or BH's, no need to wake BH */ __cpu_raise_softirq (this_cpu,NET_RX_SOFTIRQ ); -local_irq_restore (flags);#ifndef OFFLINE_SAMPLE get_sample_stats (this_cpu);#endif
@@ -1529,10 +1529,10 @@local_irq_disable (); netdev_rx_stat[this_cpu].time_squeeze++; +local_irq_enable ();/* This already runs in BH context, no need to wake up BH's */ __cpu_raise_softirq (this_cpu,NET_RX_SOFTIRQ ); -local_irq_enable ();NET_PROFILE_LEAVE (softnet_process);return ;
Quoted 4 lines
Quoted 1 line
I cannot see any problem.
Why not fix all the cases? Why have this wierd secret rule that
cpu_raise_softirq()should not be called with irqs disabled?
cpu_raise_softirq _can_ be called with irq disabled too just now, irq enabled or disabled has no influence at all on cpu_raise_softirq.
The fact you are running on a irq handler or not has influence instead, if you are running in a irq handler do_IRQ will take care of the latency, if you are running in normal kernel code ksoftirqd will take care of the latency, and both cases are handled perfectly right.
Quoted 2 lines
Call me old-fashioned, but why not *fix* the problem, if you're going to rewrite this code... again...
There's no problem at all to fix, everything is just fine from 2.4.7, period.
Andrea
In message <[email protected]> you write:
Quoted 5 lines
Quoted 2 lines
Why not fix all the cases? Why have this wierd secret rule that
cpu_raise_softirq()should not be called with irqs disabled?
cpu_raise_softirq_can_ be called with irq disabled too just now, irq enabled or disabled has no influence at all oncpu_raise_softirq.
No, you are wrong. If I do (NOT in a hw interrupt handler):
local_irq_save(flags); ... cpu_raise_softirq(smp_processor_id(), FOO_SOFTIRQ); ... local_irq_restore(flags);
ksoftirqd won't get woken, and the FOO soft irq won't get run until the next interrupt comes in. You solved (horribly) the analagous case for local_bh_disable/enable, but not this one.
Below as suggested in my previous email (x86 only, untested). I also added a couple of comments. There's still the issue of stack overflows if you get hit hard enough with interrupts (do_softirq is exposed to reentry for short periods), but that's separate.
Linus, please consider,
Rusty.
--
Premature optmztion is rt of all evl. --DK
arch/i386/kernel/irq.c +2 -0
@@ -576,6 +576,7 @@unsigned int status; kstat.irqs[cpu][irq]++; +in_hw_irq (cpu) =1 ;spin_lock (&desc->lock); desc->handler->ack (irq);/*
@@ -633,6 +634,7 @@*/ desc->handler->end (irq);spin_unlock (&desc->lock); +in_hw_irq (cpu) =0 ;if (softirq_pending (cpu))do_softirq ();
include/asm-i386/hardirq.h +1 -0
@@ -13,6 +13,7 @@unsigned int __syscall_count;struct task_struct * __ksoftirqd_task;/* waitqueue is too large */ unsigned int __nmi_count;/* arch dependent */ +int __in_hw_irq; }____cacheline_aligned irq_cpustat_t;#include <linux/irq_cpustat.h> /* Standard mappings for irq_cpustat_t above */
include/asm-i386/softirq.h +2 -33
@@ -4,46 +4,15 @@#include <asm/atomic.h> #include <asm/hardirq.h> -#define __cpu_bh_enable (cpu) \ +#define cpu_bh_enable (cpu) \ do { barrier(); local_bh_count(cpu)--; } while (0)#define cpu_bh_disable (cpu) \ do { local_bh_count(cpu)++; barrier(); } while (0)#define local_bh_disable () cpu_bh_disable(smp_processor_id()) -#define __local_bh_enable () __cpu_bh_enable(smp_processor_id()) +#define local_bh_enable () cpu_bh_enable(smp_processor_id())#define in_softirq () (local_bh_count(smp_processor_id()) != 0) - -/* -* NOTE: this assembly code assumes: -* -* (char *)&local_bh_count - 8 == (char *)&softirq_pending -* -* If you change the offsets in irq_stat then you have to -* update this code as well. -*/ -#define local_bh_enable() \ -do { \ - unsigned int *ptr = &local_bh_count(smp_processor_id()); \ - \ - barrier(); \ - if (!--*ptr) \ - __asm__ __volatile__ ( \ - "cmpl $0, -8(%0);" \ - "jnz 2f;" \ - "1:;" \ - \ - ".section .text.lock,\"ax\";" \ - "2: pushl %%eax; pushl %%ecx; pushl %%edx;" \ - "call %c1;" \ - "popl %%edx; popl %%ecx; popl %%eax;" \ - "jmp 1b;" \ - ".previous;" \ - \ - : /* no output */ \ - : "r" (ptr), "i" (do_softirq) \ - /* no registers clobbered */ ); \ -} while (0)#define __cpu_raise_softirq (cpu, nr) __set_bit(nr, &softirq_pending(cpu))
include/linux/irq_cpustat.h +1 -0
@@ -31,6 +31,7 @@#define local_bh_count (cpu) __IRQ_STAT((cpu), __local_bh_count)#define syscall_count (cpu) __IRQ_STAT((cpu), __syscall_count)#define ksoftirqd_task (cpu) __IRQ_STAT((cpu), __ksoftirqd_task) +#define in_hw_irq (cpu) __IRQ_STAT((cpu), __in_hw_irq)/* arch dependent irq_stat fields */ #define nmi_count (cpu) __IRQ_STAT((cpu), __nmi_count)/* i386, ia64 */
kernel/softirq.c +8 -11
@@ -68,6 +68,7 @@long flags;__u32 mask; +/* Prevent reentry: we always hold an irq or bh count */ if (in_interrupt ())return ;
@@ -102,8 +103,10 @@ mask &= ~pending; goto restart; } -__local_bh_enable (); +local_bh_enable (); +/* More came in while we were processing? Let +softirqd handle the overload. */ if (pending)wakeup_softirqd (cpu); }
@@ -115,16 +118,10 @@ {__cpu_raise_softirq (cpu, nr); -/* -* If we're in an interrupt or bh, we're done -* (this also catches bh-disabled code). We will -* actually run the softirq once we return from -* the irq or bh. -* -* Otherwise we wake up ksoftirqd to make sure we -* schedule the softirq soon. -*/ -if (!(local_irq_count (cpu) |local_bh_count (cpu))) +/* If we're in an interrupt handler, then softirqs get +processed on the way out. For all other (rarer) cases, +wake softirqd to deal with it soon. */ +if (!in_hw_irq (cpu))wakeup_softirqd (cpu); }
Quoted 9 lines · Rusty Russell
In message <[email protected]> you write:
Quoted 5 lines
Quoted 2 lines
Why not fix all the cases? Why have this wierd secret rule that
cpu_raise_softirq()should not be called with irqs disabled?
cpu_raise_softirq_can_ be called with irq disabled too just now, irq enabled or disabled has no influence at all oncpu_raise_softirq.No, you are wrong. If I do (NOT in a hw interrupt handler):
Quoted 7 lines
local_irq_save(flags); ... cpu_raise_softirq(smp_processor_id(), FOO_SOFTIRQ); ... local_irq_restore(flags);ksoftirqd won't get woken, and the FOO soft irq won't get run until
You are wrong, please check again all the code involved.
inline void cpu_raise_softirq(unsigned int cpu, unsigned int nr)
{
__cpu_raise_softirq(cpu, nr);
/*
* If we're in an interrupt or bh, we're done
* (this also catches bh-disabled code). We will
* actually run the softirq once we return from
* the irq or bh.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
*/
if (!(local_irq_count(cpu) | local_bh_count(cpu)))
wakeup_softirqd(cpu);
}If you are not in hw interrupt local_irq_count is zero so you will run wakeup_softirqd().
The fact irq are enabled or disabled has no influence on the logic.
Quoted 2 lines
the next interrupt comes in. You solved (horribly) the analagous case for local_bh_disable/enable, but not this one.
I didn't changed at all local_bh_enable (except a fix for a missing barrier()), local_bh_enable/disable was solved by Ingo in 2.4.6.
Quoted 1 line
Below as suggested in my previous email (x86 only, untested). I also
It seems you're duplicating the local_irq_count functionalty plus you break local_bh_enable, from local_bh_enable you want to run the softirq immediatly.
Quoted 3 lines
added a couple of comments. There's still the issue of stack overflows if you get hit hard enough with interrupts (
do_softirqis exposed to reentry for short periods), but that's separate.
do_softirq can be re-entered but on re-entry it will return immediatly because local_bh_count will be non zero in such case, so the only stack overhead of a re-entry is a few words so it cannot harm (if stack overflows it cannot be because of do_softirq re-entry).
Andrea
In message <[email protected]> you write:
Quoted 1 line
You are wrong, please check again all the code involved.
Apologies: you are absolutely correct, disregard my crap patch. local_irq_save does not inc local_irq_count, unlike local_bh_disable. Oops.
Quoted 1 line
do_softirqcan be re-entered but on re-entry it will return immediatly
"immediately" leaves a narrow window. Sure, it'd be really hard to hit, but it's still there.
Rusty.
--
Premature optmztion is rt of all evl. --DK
Quoted 5 lines · David S. Miller
Quoted 3 lines · Rusty Russell
In message <[email protected]> you write:
Quoted 1 line
What kernel are you looking at? There's no such code in 2.4.7, the only
Quoted 7 lines
Quoted 1 line
Oh, so it's only a trap *waiting* to happen. That's OK then!
...
Quoted 2 lines
Why not fix all the cases? Why have this wierd secret rule that
cpu_raise_softirq()should not be called with irqs disabled?Why keep it secret? I think Andrea is exactly right here, and we should just comment this restriction. That's all.
there's not such restriction as far I can tell (see my previous email for details).
Andrea
Hello!
Quoted 1 line
The first
netif_rxis required to run from interrupt handler
No! netif_rx() is called from _any_ context. Check with grep. So, this must be repaired in some way.
Actually, assumption that local_bh_enable() etc does not happen with disabled irq was the biggest hole in Ingo's patch: all the functions ever doing local_irq_save() assume that they _can_ be called with disabled irqs (otherwise they would make local_irq_disable() instead) and, hence, some spinlocks held.
Quoted 2 lines
for the next interrupt (in old 2.4 kernels you had to wait for the next irq instead).
Well, not "had to", a small delay was purpose of this call yet. Actually, now it can be replaced with a direct schedule ksoftirqd, because we surely have softirq flood, when this place is reached.
Alexey
Quoted 5 lines · Alexey Kuznetsov
Hello!
Quoted 1 line
The first
netif_rxis required to run from interrupt handlerNo!
netif_rx()is called from _any_ context. Check with grep.
Originally it was a cpu_raise_softirq, but David asked to put the __ so Linus added the comment as well:
/* Runs from irqs or BH's, no need to wake BH */
At that time I checked loopback that runs under the bh so it's ok too.
Quoted 1 line
So, this must be repaired in some way.
Yes. If ethertap or others runs outside bh and irq they could use if (pending) do_softirq by hand (or as worse wakeup ksoftirqd by hand) after netif_rx.
Quoted 2 lines
Actually, assumption that
local_bh_enable()etc does not happen with disabled irq was the biggest hole in Ingo's patch: all the functions
I hoped it was never the case because when you serialize against bh it's because you are using the bh logic instead of irqs and the whole point of the bh logic is to left irq enabled. but I'm not surprised some problem actually triggered because of this change.
Andrea
Hello!
Quoted 1 line
At that time I checked loopback that runs under the bh so it's ok too.
Well, it was not alone. I just looked at couple of places, when netif_rx was used. One is right, another (looping multicasts) is wrong. :-)
So, is plain raising softirq and leaving it raised before return to normal context not a bug? If so, then no problems. The worst, which can happen is that it will work as earlier, right? And we are allowed to yuild bhs at any point, when we desire. Nice.
Actually, also I was afraid opposite thing: netif_rx was used to allow to restart processing of skb, when we were in wrong context or were afraid recursion. And the situation, when it is called with disabled irqs and/or raised spinlock_irq (it was valid very recently!), is undetectable. Actually, I hope such places are absent, networking core does not use irq protection at all, except for netif_rx() yet. :-)
Quoted 1 line
after
netif_rx.
But why not to do just local_bh_disable(); netif_rx(); local_bh_enable()? Is this not right?
Alexey
Quoted 4 lines · [email protected] wrote:
Quoted 1 line
after
netif_rx.But why not to do just
local_bh_disable();netif_rx();local_bh_enable()? Is this not right?
that's fine, and was one of the suggested solutions during the earlier discussion of Andrea's fix.
Signature · 3 lines
Jeff Garzik | "Mind if I drive?" -Sam Building 1024 | "Not if you don't mind me clawing at the dash MandrakeSoft | and shrieking like a cheerleader." -Max
Quoted 2 lines · [email protected] writes:
So, is plain raising softirq and leaving it raised before return to normal context not a bug? If so, then no problems.
Do you mean "user context" when you say normal? Or just arbitrary non-interrupt context. In fact, to me, no specific execution context stands out with description of "normal". All of them are normal :-))))
Quoted 4 lines
Quoted 1 line
after
netif_rx.But why not to do just
local_bh_disable();netif_rx();local_bh_enable()? Is this not right?
As Jeff mentioned, this is the cure we agreed to in earlier softirq postings.
The reason I pushed to have netif_FOO use __cpu_raise_softirq() was that I felt sick to my stomache when I saw a new whole function call added to that spot. It is one of the most imporant paths in packet processing, and it runs regardless of protocol you use (well, except perhaps AF_UNIX :-)))
Let us just fix the odd places that aren't calling things in the correct environment.
Later,
David S. Miller
[email protected]
Hello!
Quoted 1 line
Do you mean "user context" when you say normal?
Why "user" then? :-)
Well, after some digging in brains I find that I use home-made terminology. non-interrupt/non-bh context is called "process context" here. :-) "Normal context" is process context with enabled BHs and irqs.
Quoted 3 lines
The reason I pushed to have
netif_FOOuse__cpu_raise_softirq()was that I felt sick to my stomache when I saw a new whole function call added to that spot.
I experience even more unpleasant feelings, when thinking what would happen if netif_rx() without these __ is called from under irq protected spinlock.
Quoted 2 lines
Let us just fix the odd places that aren't calling things in the correct environment.
I caught you! :-) Each "context" is normal, but "environment" still can be wrong.
Yes, I agreed. It is the simplest solution. In any case, all the instances of netif_rx are to be checked for spinlock-safeness.
Alexey
Quoted 2 lines · Maksim Krasnyanskiy
Should we then create generic function (something like
netif_rx_from_user) than will calldo_softirqafter callingnetif_rx?
creating such a function is certainly ok (it must first check pending() before running do_softirq of course). The name shouldn't be "from user" because we actually call it from normal kernel context.
Quoted 2 lines
I queue it and do
tasklet_schedule(tx_task). Everything works just fine but on SMP machine I noticed that sometimes data is sent in the wrong order. And the only reason why reordering could happen is if severaltx_tasksare runing at the
Do you use tasklet_enable? This patch fixes a bug in tasklet_enable. (bug found by David Mosemberg) We are thinking at more CPU friendly ways to handle the tasklet_disable, Linus just had a suggestion, but I don't have time right now to think much about the alternate approches (i'm at ols), I will do next week. If you are usng tasklet_enable you may want to give it a spin.
include/linux/interrupt.h +6 -4
@@ -139,24 +139,26 @@static inline void tasklet_disable_nosync (struct tasklet_struct *t) {atomic_inc (&t->count); +smp_mb__after_atomic_inc (); }static inline void tasklet_disable (struct tasklet_struct *t) {tasklet_disable_nosync (t);tasklet_unlock_wait (t); +smp_mb (); }static inline void tasklet_enable (struct tasklet_struct *t) { -if (atomic_dec_and_test (&t->count)) -tasklet_schedule (t); +smp_mb__before_atomic_dec (); +atomic_dec (&t->count); }static inline void tasklet_hi_enable (struct tasklet_struct *t) { -if (atomic_dec_and_test (&t->count)) -tasklet_hi_schedule (t); +smp_mb__before_atomic_dec (); +atomic_dec (&t->count); }extern void tasklet_kill (struct tasklet_struct *t);
Andrea
Maksim Krasnyanskiy
AppliedQuoted 7 lines
Quoted 3 lines
Should we then create generic function (something like
netif_rx_from_user) than will calldo_softirqafter callingnetif_rx?creating such a function is certainly ok (it must first check
pending()before runningdo_softirqof course). The name shouldn't be "from user" because we actually call it from normal kernel context.
Sure.
Quoted 4 lines
Quoted 2 lines
I queue it and do
tasklet_schedule(tx_task). Everything works just fine but on SMP machine I noticed that sometimes data is sent in the wrong order. And the only reason why reordering could happen is if severaltx_tasksare runing at theDo you use
tasklet_enable?
Yep. To sync rx and tx tasks.
Quoted 6 lines
This patch fixes a bug in
tasklet_enable.
(bug found by David Mosemberg) We are thinking at more CPU friendly ways to handle thetasklet_disable, Linus just had a suggestion, but I don't have time right now to think much about the alternate approches (i'm at ols), I will do next week. If you are usngtasklet_enableyou may want to give it a spin.
Applied to 2.4.8-pre1. Didn't make any difference.
Also it doesn't fix the scenario that I described (reschedule while running). I'm still wondering why don't I hit that trylock/BUG in tasklet_action.
Thanks
Max
Maksim Krasnyanskiy
Senior Kernel Engineer
Qualcomm Incorporated
Hello!
Quoted 1 line
Applied to 2.4.8-pre1. Didn't make any difference.
Yes, that hole is mostly theoretical. At least, on intel. Seems, gcc is still not enough clever to reorder atomic operations.
Quoted 2 lines
Also it doesn't fix the scenario that I described (reschedule while running). I'm still wondering why don't I hit that trylock/BUG in
tasklet_action.
How old the problem is? Was it always present?
To be honest, this is too strong bug to believe to this at all. :-)
Alexey
Quoted 5 lines
Quoted 3 lines
Also it doesn't fix the scenario that I described (reschedule while running). I'm still wondering why don't I hit that trylock/BUG in
tasklet_action.How old the problem is ? Was it always present ?
That's a good question. Data ordering check in Bluetooth tools was introduced pretty recently. So, before 2.4.7 I wasn't paying attention to it and there for didn't notice any problems with tasklets.
Quoted 1 line
To be honest, this is too strong bug to believe to this at all. :-)
:) Agree. But I checked all my code, tx_task is called from tasklet only. And I do see that it's getting run on several cpus at the same time.
Also don't you agree with that it's possible (at least in theory) to hit that trylock/BUG in tasklet_action ?
Max
Maksim Krasnyanskiy
Senior Kernel Engineer
Qualcomm Incorporated
Hello!
Quoted 1 line
Also don't you agree with that it's possible (at least in theory) to hit that trylock/BUG in
tasklet_action?
Alas, I am not an expert in this area after Ingo's patch. Let's learn together. At first sight, it must crash at this BUG() instead of serialization, indeed. :-)
I am still afraid to boot kernels after 2.4.5. Feel discomfort. :-)
Alexey
Quoted 7 lines
Quoted 2 lines
Also don't you agree with that it's possible (at least in theory) to hit that trylock/BUG in
tasklet_action?Alas, I am not an expert in this area after Ingo's patch. Let's learn together. At first sight, it must crash at this
BUG()instead of serialization, indeed. :-)I am still afraid to boot kernels after 2.4.5. Feel discomfort. :-)
You have a very valid reasons for this :).
I just tried 2.4.5 and guess what, it works _correctly_ e.g. it does not run same tasklet on several cpus.
I looked at the tasklet_schedule and friends. And what I found surprised me quite a bit. How did it went in ;) Somebody misunderstood the purpose of the STATE_RUN (tasklet_trylock/unlock) and STATE_SCHED bits. Here is how it should work (Alex correct me if I'm wrong here).
Purpose of STATE_SCHED is to protect tasklet from being _scheduled_ on the several cpus at the same time. When we run a tasklet we unlink it from the queue and clear STATE_SCHED to allow it to be scheduled again. Purpose of STATE_RUN is to protect tasklet from being _run_ on the several cpu at the same time. Before we run a tasklet we set STATE_RUN (tasklet_trylock) and we clear STATE_RUN(tasklet_unlock) when we're done with this tasklet.
In other word we run tasklet with STATE_RUN set (locked tasklet) but with STATE_SCHED cleared.
In 2.4.7 and 2.4.8pre1 (and probably in 2.4.6)tasklet_schedule calls tasklet_unlock after it schedules tasklet, which is _totaly_ wrong. (that's why I was not hitting that trylock/BUG in tasklet_action)tasklet_action assumes that if we found locked tasklet in our sched queue it's a BUG which is again wrong. Locked tasklet in the sched queue is prefectly fine, it just means that tasklet is still running on other cpu and we should just reschedule it (and that's what original code does btw).
Also I think uninlining tasklet_schedule was not a good idea. It's kinda critical especially for already scheduled case. We make a function call just to find out that tasklet is already scheduled. And there is no point in call disable_irq if we're not gonna schedule tasklet. And there is no point in locking tasklet on the UP machines.
So, the patch (Alex you can safely boot latest kernels now :)). It restores original (correct) functionality and makes tasklet code a little more readable. It also includes patch to tasklet_enable/disable suggested by Andrea.
Tested on my dual PPro SMP box, PII UP and Sparc64 UP.
Linus, please consider applying it because current code is _broken_.
(http://bluez.sf.net/tasklet_patch.gz in case my mailer messed it up)
Thanks
Max
kernel/softirq.c Fri Jul 27 15:57:50 2001 renamed from kernel/softirq.c.old Fri Jul 27 17:19:52 2001 +17 -58
@@ -140,43 +140,7 @@ /* Tasklets */ struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned; - -void tasklet_schedule(struct tasklet_struct *t) -{ - unsigned long flags; - int cpu; - - cpu = smp_processor_id(); - local_irq_save(flags); - /* - * If nobody is running it then add it to this CPU's - * tasklet queue. - */ - if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) { - t->next = tasklet_vec[cpu].list; - tasklet_vec[cpu].list = t; - cpu_raise_softirq(cpu, TASKLET_SOFTIRQ); - tasklet_unlock(t); - } - local_irq_restore(flags); -} - -void tasklet_hi_schedule(struct tasklet_struct *t) -{ - unsigned long flags; - int cpu; - - cpu = smp_processor_id(); - local_irq_save(flags); - - if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) { - t->next = tasklet_hi_vec[cpu].list; - tasklet_hi_vec[cpu].list = t; - cpu_raise_softirq(cpu, HI_SOFTIRQ); - tasklet_unlock(t); - } - local_irq_restore(flags); -} +struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned; static void tasklet_action(struct softirq_action *a) {
@@ -193,16 +157,16 @@ list = list->next; - if (!tasklet_trylock(t)) - BUG(); - if (!atomic_read(&t->count)) { - if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) - BUG(); - t->func(t->data); + if (tasklet_trylock(t)) { + if (!atomic_read(&t->count)) { + if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) + BUG(); + t->func(t->data); + tasklet_unlock(t); + continue; + } tasklet_unlock(t); - continue; } - tasklet_unlock(t); local_irq_disable(); t->next = tasklet_vec[cpu].list;
@@ -212,10 +176,6 @@ } } - - -struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned; - static void tasklet_hi_action(struct softirq_action *a) { int cpu = smp_processor_id();
@@ -231,16 +191,16 @@ list = list->next; - if (!tasklet_trylock(t)) - BUG(); - if (!atomic_read(&t->count)) { - if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) - BUG(); - t->func(t->data); + if (tasklet_trylock(t)) { + if (!atomic_read(&t->count)) { + if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) + BUG(); + t->func(t->data); + tasklet_unlock(t); + continue; + } tasklet_unlock(t); - continue; } - tasklet_unlock(t); local_irq_disable(); t->next = tasklet_hi_vec[cpu].list;
@@ -249,7 +209,6 @@ local_irq_enable(); } } - void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
include/linux/interrupt.h Fri Jul 27 17:26:37 2001 renamed from include/linux/interrupt.h.old Fri Jul 27 17:20:11 2001 +62 -18
111 lines not on this page · raw message
-----------------
Maksim Krasnyanskiy
Senior Kernel Engineer
Qualcomm Incorporated
Hello!
Quoted 1 line
Here is how it should work (Alex correct me if I'm wrong here).
You _were_ right. Now... I still do not know, I can only comment and state that current code really looks funny. :-)
Quoted 1 line
Purpose of
STATE_SCHEDis to protect tasklet from being _scheduled_ on the several cpus at the same time.
Rather its purpose was not protective. When it was set, it meaned that the function _will_ be run. tasklet_action was allowed to reset it at any time before function is called. But not after, of course.
Quoted 1 line
When we run a tasklet we unlink it from the queue and clear
STATE_SCHEDto allow it to be scheduled again.
This can be made later, but before the function is called.
Quoted 1 line
tasklet_schedulecallstasklet_unlockafter it schedules tasklet,
Hmm... but this opens one more bug: are schedules not lost, when they are made while tasklet is running?
Quoted 1 line
we're not gonna schedule tasklet. And there is no point in locking tasklet on the UP machines.
It can be converted to spinlock. I felt a discomfort creating spinlock, which never spins. :-)
And one more question:
kernel/softirq.c Fri Jul 27 15:57:50 2001
Quoted 4 lines
kernel/softirq.c Fri Jul 27 15:57:50 2001 renamed from kernel/softirq.c.old Fri Jul 27 17:19:52 2001 +0
-4- cpu_raise_softirq(cpu, TASKLET_SOFTIRQ); <<<< - tasklet_unlock(t); - } - local_irq_restore(flags); <<<<
But Andrea has just tought me that this is invalid to call cpu_raise_softirq in such context. No differences of netif_rx() here, all the issues are the same.
Quoted 1 line
(Alex you can safely boot latest kernels now :)).
Thank you. I am not afraid of booting not-working kernels, even like this. :-) I am afraid, when do not feel ground. After your analysis even direction to ground is lost. :-)
Alexey
Quoted 5 lines
Quoted 2 lines
tasklet_schedulecallstasklet_unlockafter it schedules tasklet,Hmm... but this opens one more bug: are schedules not lost, when they are made while tasklet is running ?
Yes it does.
btw Here is an idea. May be we should reschedule tasklet on the same cpu it's running on. It should probably improve cache usage and stuff.
I'm thinking about something like this.
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (test_bit(TASKLET_STATE_RUN, &t->state)) {
set_bit(TASKLET_NEED_RESCHED, &t->state);
} else if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
int cpu = smp_processor_id();
unsigned long flags;
local_irq_save(flags);
t->next = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = t;
cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
}static void tasklet_action(struct softirq_action *a)
{
int cpu = smp_processor_id();
struct tasklet_struct *list;
local_irq_disable();
list = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = NULL;
local_irq_enable();
while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data);
tasklet_unlock(t);
if (test_and_clear_bit(TASKLET_NEED_RESCHED, &t->state)
goto resched;
continue;
}
tasklet_unlock(t);
}resched:
local_irq_disable();
t->next = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = t;
cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_enable();
}
}There is small window there but this is just rfc. So if tasklet is already running we set NEED_RESCHED bit and tasklet_action reschedules tasklet on the same cpu. (currently we may reschedule it on anther cpu).
Comments ?
Max
Maksim Krasnyanskiy
Senior Kernel Engineer
Qualcomm Incorporated
Quoted 8 lines · On Sat, Jul 28, 2001 at 09:41:41PM +0400, [email protected] wrote:
kernel/softirq.c Fri Jul 27 15:57:50 2001
Quoted 4 lines
kernel/softirq.c Fri Jul 27 15:57:50 2001 renamed from kernel/softirq.c.old Fri Jul 27 17:19:52 2001 +0
-4- cpu_raise_softirq(cpu, TASKLET_SOFTIRQ); <<<< - tasklet_unlock(t); - } - local_irq_restore(flags); <<<<But Andrea has just tought me that this is invalid to call
cpu_raise_softirqin such context. No differences ofnetif_rx()here, all the issues are the same.
cpu_raise_softirq is valid in any context. calling cpu_raise_softirq there was correct (__cpu_raise_softirq would been too weak).
For performance reasons we should instead __cpu_raise_sofirq (instead of cpu_raise_softirq) in tasklet_action that runs within do_softirq but that is a minor optimization.
Quoted 2 lines
I am afraid, when do not feel ground. After your analysis even direction to ground is lost. :-)
:) As far I can see returning to the correct 2.4.5 tasklet logic will fix the tasklet problem (only tasklets had a problem in 2.4.7).
Andrea
Hello!
Quoted 2 lines
cpu_raise_softirqis valid in any context. callingcpu_raise_softirqthere was correct (__cpu_raise_softirqwould been too weak).
I see now, the picture clears.
Quoted 1 line
fix the tasklet problem (only tasklets had a problem in 2.4.7).
I said the problem was not in code. In understanding this. I am still not 100% sure what is legal, what is not. :-)
F.e. Andrea, teach me how to make the following thing (not for released kernel, for me): I want to schedule softirq, but I do not want that this softirq eat all the cpu. It looks natural to use ksoftirqd for this.
Alexey
Quoted 3 lines · On Sat, Jul 28, 2001 at 11:02:07PM +0400, [email protected] wrote:
F.e. Andrea, teach me how to make the following thing (not for released kernel, for me): I want to schedule softirq, but I do not want that this softirq eat all the cpu. It looks natural to use ksoftirqd for this.
yes, ksoftirqd should just avoid you to eat all the cpu even if you keep posting the softirq all the time. ksoftirqd is reniced at +20 so it should be pretty nice with the other tasks in the system.
If you want to delay the softirq and run it at a low frequency then you should use a timer or another functionality (the softirq is required to run ASAP always).
I hope I didn't misunderstood the question in such case please correct me.
Andrea
PS. I will be offline shortly so I may not be able to answer further emails until Monday.
Hello!
Quoted 1 line
yes, ksoftirqd should just avoid you to eat all the cpu
I see now, you completely killed dead loops from Ingo's patch! It is your original approach and I am happy with it.
Before falling to euforia, the last question:
Is Ingo really happy with this? He blamed about latency, it is not better than in 2.4.5 (with cpu_idle fix) :-)
Quoted 2 lines
I hope I didn't misunderstood the question in such case please correct me.
You really misunderstood this a bit, but solely because the question was meaningless in context of 2.4.7. My apologies. :-)
Alexey
Quoted 4 lines · Alexey Kuznetsov
Before falling to euforia, the last question:
Is Ingo really happy with this? He blamed about latency, it is not better than in 2.4.5 (withcpu_idlefix) :-)
I think the latency issue was really the fact that we weren't always running softirqs in a timely fashion after they had been disabled by a "disable_bh()". That is fixed with the new softirq stuff, regardless of the other issues.
But it would be good to have some specweb runs etc done to verify.
Ingo?
Linus
Hello!
Quoted 4 lines
I think the latency issue was really the fact that we weren't always running softirqs in a timely fashion after they had been disabled by a "
disable_bh()". That is fixed with the new softirq stuff, regardless of the other issues.
I hope too. Actually, this observation was main argument pro ksoftirqd and against instant restart. Ingo objected but finally the issue was buried... or I lost discussion not reading maillists for some time.
Alexey
Quoted 5 lines
Quoted 5 lines
I think the latency issue was really the fact that we weren't always running softirqs in a timely fashion after they had been disabled by a "
disable_bh()". That is fixed with the new softirq stuff, regardless of the other issues.
nope. i observed latency issues with restart + ksoftirqd as well. [when i first saw these latency problems i basically had ksoftirqd implemented independently from your patch, and threw the idea away because it was insufficient from the latency point of view.] Those latencies are harder to observe because they are not 1/HZ anymore but several hundred millisecs at most. Plus, like i said previously, pushing IRQ context work into a scheduler-level context 'feels' incorrect to me - it only makes the latencies less visible. I'll do some measurements.
Ingo
Quoted 5 lines · Ingo Molnar
Quoted 4 lines
Quoted 4 lines
I think the latency issue was really the fact that we weren't always running softirqs in a timely fashion after they had been disabled by a "
disable_bh()". That is fixed with the new softirq stuff, regardless of the other issues.
Quoted 8 lines
nope. i observed latency issues with restart + ksoftirqd as well. [when i first saw these latency problems i basically had ksoftirqd implemented independently from your patch, and threw the idea away because it was insufficient from the latency point of view.] Those latencies are harder to observe because they are not 1/HZ anymore but several hundred millisecs at most. Plus, like i said previously, pushing IRQ context work into a scheduler-level context 'feels' incorrect to me - it only makes the latencies less visible. I'll do some measurements.
If you schedule ksoftirqd as SCHED_FIFO or SCHED_RR, and run with the preemptible kernel patch, you can get maximum latencies down to a few hundred microseconds most of the time, with typical latencies of a few
tens of microseconds. Perhaps you could also measure that
configuration? (Latest preemptible kernel patch is available at kpreempt.sourceforge.net).
I'd like to see all the various execution contexts of Linux (irqs, softirqs, tasklets, kernel daemons) all become (real-time where necessary) kernel threads like ksoftirqd, scheduled with the appropriate scheduling class and priority. The resulting kernel code would be much simpler and more maintainable; and it would make it possible to change the scheduling priority of the threads to optimize for different application loads.
Nigel Gamble [email protected]
Mountain View, CA, USA. http://www.nrg.org/
MontaVista Software [email protected]
Quoted 7 lines · In article <[email protected]> you wrote:
I'd like to see all the various execution contexts of Linux (irqs, softirqs, tasklets, kernel daemons) all become (real-time where necessary) kernel threads like ksoftirqd, scheduled with the appropriate scheduling class and priority. The resulting kernel code would be much simpler and more maintainable; and it would make it possible to change the scheduling priority of the threads to optimize for different application loads.
That's how solaris does it, btw. From time to time sun seems to get something right ;)
Christoph
Signature · 1 line
Whip me. Beat me. Make me maintain AIX.
Hello!
Quoted 1 line
nope. i observed latency issues with restart + ksoftirqd as well.
Ingo, what is impact on total performance yet?
Just to remind: simple constantation of the fact that "latency is bad" still does not imply that dead loops are inevitable.
We really could try to complicate policing of softirqs (f.e. counting cycles on them and breaking loop with falling to ksoftirqd after it preempts threads for more than some threshold). But it is still not clear that it is worth to do. If you see an impact, let's try this way.
Probably, priority on ksoftirqd should be tuned. Seems, it still should have high priority, a bit less than real-time.
Alexey
Quoted 1 line · Maksim Krasnyanskiy
tasklet_schedulecallstasklet_unlockafter it schedules tasklet, which is _totaly_ wrong.
yes, it was a leftover from 2.4.6. Your diff -u 2.4.7 2.4.5 is correct and I also suggest it for for mainline inclusion, thanks.
Andrea
Quoted 5 lines · Andrea Arcangeli
Quoted 1 line · Maksim Krasnyanskiy
tasklet_schedulecallstasklet_unlockafter it schedules tasklet, which is _totaly_ wrong.yes, it was a leftover from 2.4.6. Your diff -u 2.4.7 2.4.5 is correct and I also suggest it for for mainline inclusion, thanks.
I did some minor further improvement to it while merging it into my tree and then I rediffed it. This one also adds the debug check for local_bh_enable in a clied region. (note for other archs:__cpu_raise_softirq is required to be atomic with respect to irqs but it doesn't need to be atomic with respect to smp, in x86 it is implemented as a non locked bts for example)
include/asm-i386/softirq.h +4 -0
@@ -25,7 +25,11 @@#define local_bh_enable () \ do { \ unsigned int *ptr = &local_bh_count(smp_processor_id()); \ + unsigned long flags; \ \ + __save_flags(flags); \ + if (!(flags & (1 << 9))) \ + BUG(); \ barrier(); \ if (!--*ptr) \ __asm__ __volatile__ ( \
include/linux/interrupt.h +56 -10
97 lines not on this page · raw message
kernel/softirq.c +19 -59
113 lines not on this page · raw message
net/core/dev.c +2 -2
@@ -1217,10 +1217,10 @@ enqueue:dev_hold (skb->dev);__skb_queue_tail (&queue->input_pkt_queue,skb); +local_irq_restore (flags);/* Runs from irqs or BH's, no need to wake BH */ __cpu_raise_softirq (this_cpu,NET_RX_SOFTIRQ ); -local_irq_restore (flags);#ifndef OFFLINE_SAMPLE get_sample_stats (this_cpu);#endif
@@ -1529,10 +1529,10 @@local_irq_disable (); netdev_rx_stat[this_cpu].time_squeeze++; +local_irq_enable ();/* This already runs in BH context, no need to wake up BH's */ __cpu_raise_softirq (this_cpu,NET_RX_SOFTIRQ ); -local_irq_enable ();NET_PROFILE_LEAVE (softnet_process);return ;
Andrea
Quoted 10 lines · On Thu, Jul 26, 2001 at 09:46:52PM +0400, [email protected] wrote:
Hello!
Quoted 1 line
At that time I checked loopback that runs under the bh so it's ok too.
Well, it was not alone. I just looked at couple of places, when
netif_rxwas used. One is right, another (looping multicasts) is wrong. :-)So, is plain raising softirq and leaving it raised before return to normal context not a bug? If so, then no problems. The worst, which can happen is that it will work as earlier, right?
Depends what you mean with 'normal context'. If you mean 'userspace context' then it is a bug, and in 2.4.5 we would been catching that case in entry.S.
If there are lots of users of netif_rx outside bh or irq context I guess this is the simpler way is:
net/core/dev.c +4 -4
@@ -1217,10 +1217,10 @@ enqueue:dev_hold (skb->dev);__skb_queue_tail (&queue->input_pkt_queue,skb); +local_irq_restore (flags);/* Runs from irqs or BH's, no need to wake BH */ -__cpu_raise_softirq (this_cpu,NET_RX_SOFTIRQ ); -local_irq_restore (flags); +cpu_raise_softirq (this_cpu,NET_RX_SOFTIRQ );#ifndef OFFLINE_SAMPLE get_sample_stats (this_cpu);#endif
@@ -1529,10 +1529,10 @@local_irq_disable (); netdev_rx_stat[this_cpu].time_squeeze++; +local_irq_enable ();/* This already runs in BH context, no need to wake up BH's */ -__cpu_raise_softirq (this_cpu,NET_RX_SOFTIRQ ); -local_irq_enable (); +cpu_raise_softirq (this_cpu,NET_RX_SOFTIRQ );NET_PROFILE_LEAVE (softnet_process);return ;
Quoted 6 lines
And we are allowed to yuild bhs at any point, when we desire. Nice.
Actually, also I was afraid opposite thing:
netif_rxwas used to allow to restart processing of skb, when we were in wrong context or were afraid recursion. And the situation, when it is called with disabled irqs and/or raisedspinlock_irq(it was valid very recently!), is undetectable.
It should be detectable with this debugging code (untested but trivially fixable if it doesn't compile):
include/asm-i386/softirq.h renamed from include/asm-i386/softirq.h.~1~ +4 -0
@@ -25,7 +25,11 @@#define local_bh_enable () \ do { \ unsigned int *ptr = &local_bh_count(smp_processor_id()); \ + unsigned long flags; \ \ + __save_flags(flags); \ + if (!(flags & (1 << 9))) \ + BUG(); \ barrier(); \ if (!--*ptr) \ __asm__ __volatile__ ( \
Quoted 2 lines
Actually, I hope such places are absent, networking core does not use irq protection at all, except for
netif_rx()yet. :-)
I hope too :).
Quoted 4 lines
Quoted 1 line
after
netif_rx.But why not to do just
local_bh_disable();netif_rx();local_bh_enable()? Is this not right?
That is certainly right. However it is slower than just doing if (pending) do_softirq() after netif_rx().
Andrea
Quoted 3 lines
If there are lots of users of
netif_rxoutside bh or irq context I guess this is the simpler way is:
I'm not sure about a lot but there are certainly some. TUN/TAP driver for example. It calls netif_rx from user context (syscall).
Quoted 6 lines
Quoted 4 lines
Quoted 1 line
after
netif_rx.But why not to do just
local_bh_disable();netif_rx();local_bh_enable()? Is this not right?That is certainly right. However it is slower than just doing if (pending)
do_softirq()afternetif_rx().
Should we then create generic function (something like netif_rx_from_user) than will call do_softirq after calling netif_rx ?
btw I think I just found another problem with softirqs. Actually with tasklets. May be you guys can help me with that. The problem is that my tasklet is getting run on 2 cpu simultaneously and according to description in linux/interrupt.h it shouldn't.
I have a simple tx_task. All it does is sending queued stuff to the device. Whenever I need to send something I queue it and do tasklet_schedule(tx_task). Everything works just fine but on SMP machine I noticed that sometimes data is sent in the wrong order. And the only reason why reordering could happen is if several tx_tasks are runing at the same time. So I added a simple check in tasklet function to complain if it's already running.
Here is the simplified code example:
tasklet_init(&hdev->tx_task, hci_tx_task, (unsigned long) hdev);
static void hci_tx_task(unsigned long arg)
{
struct hci_dev *hdev = (struct hci_dev *) arg;
struct sk_buff *skb;
static __u32 r = 0;
if (test_and_set_bit(1, &r)) {
ERR("already running cpu %d", smp_processor_id());
return;
}
/* Send next queued packet */
while ((skb = skb_dequeue(&hdev->raw_q)))
hci_send_frame(skb);
clear_bit(1, &r);
}tasklet_schedule(&hdev->tx_task);
Now everything is in order and I'm getting bunch of "already running" messages with different cpu_id. (hci_tx_task is never called directly).
So, I looked at the tasklet_schedule and tasklet_action code. And it seems to me that tasklet can be scheduled on several cpus at the same time. Here is scenario:
tasklet X is running on cpu 1. tasklet_action clears STATE_SCHED bit and calls tasklet function. tasklet_schedule(taskletX) is called on cpu 2. Since STATE_SCHED is cleared tasklet X is scheduled. tasklet_action is called on cpu 2 (tasklet X functions is still running on cpu 1)
In that case tasklet_action on cpu 2 should hit a:
if (!tasklet_trylock(t))
BUG();because tasklet is still locked by cpu 1. For some reason it doesn't though (I don't see any "kernel bug" messages). But my tx_task is run second time.
Comments ?
Max
Maksim Krasnyanskiy
Senior Kernel Engineer
Qualcomm Incorporated
Hello!
Quoted 2 lines
If there are lots of users of
netif_rxoutside bh or irq context I guess this is the simpler way is:
I prefer to grep for netif_rx yet.
Quoted 2 lines
It should be detectable with this debugging code (untested but trivially fixable if it doesn't compile):
It is worth to add to official kernel.
Alexey