When CONFIG_PREEMPT_RT_FULL is enabled, tasklets run as threads,and spinlocks turn are mutexes. But this can cause issues withtasks disabling tasklets. A tasklet runs under ksoftirqd, andif a tasklets are disabled with tasklet_disable(), the taskletcount is increased. When a tasklet runs, it checks this counterand if it is set, it adds itself back on the softirq queue andreturns.

The problem arises in RT because ksoftirq will see that a softirqis ready to run (the tasklet softirq just re-armed itself), and willnot sleep, but instead run the softirqs again. The tasklet softirqwill still see that the count is non-zero and will not executethe tasklet and requeue itself on the softirq again, which willcause ksoftirqd to run it again and again and again.

It gets worse because ksoftirqd runs as a real-time thread.If it preempted the task that disabled tasklets, and that taskhas migration disabled, or can't run for other reasons, the taskletsoftirq will never run because the count will never be zero, andksoftirqd will go into an infinite loop. As an RT task, it thisbecomes a big problem.

This is a hack solution to have tasklet_disable stop tasklets, andwhen a tasklet runs, instead of requeueing the tasklet softirqdit delays it. When tasklet_enable() is called, and tasklets arewaiting, then the tasklet_enable() will kick the tasklets to continue.This prevents the lock up from ksoftirq going into an infinite loop.

Signed-off-by: Ingo Molnar <mingo@elte.hu>[ ported to 3.0-rt ]Signed-off-by: Steven Rostedt <rostedt@goodmis.org>--- include/linux/interrupt.h | 39 +++++---- kernel/softirq.c | 208 ++++++++++++++++++++++++++++++++------------- 2 files changed, 170 insertions(+), 77 deletions(-)diff --git a/include/linux/interrupt.h b/include/linux/interrupt.hindex a62158f..3142442 100644--- a/include/linux/interrupt.h+++ b/include/linux/interrupt.h@@ -501,8 +501,9 @@ extern void __send_remote_softirq(struct call_single_data *cp, int cpu, to be executed on some cpu at least once after this. * If the tasklet is already scheduled, but its execution is still not started, it will be executed only once.- * If this tasklet is already running on another CPU (or schedule is called- from tasklet itself), it is rescheduled for later.+ * If this tasklet is already running on another CPU, it is rescheduled+ for later.+ * Schedule must not be called from the tasklet itself (a lockup occurs) * Tasklet is strictly serialized wrt itself, but not wrt another tasklets. If client needs some intertask synchronization, he makes it with spinlocks.@@ -527,27 +528,36 @@ struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data } enum { TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */- TASKLET_STATE_RUN /* Tasklet is running (SMP only) */+ TASKLET_STATE_RUN, /* Tasklet is running (SMP only) */+ TASKLET_STATE_PENDING /* Tasklet is pending */ };

+static void inline+__tasklet_common_schedule(struct tasklet_struct *t, struct tasklet_head *head, unsigned int nr)+{+ if (tasklet_trylock(t)) {+again:+ /* We may have been preempted before tasklet_trylock+ * and __tasklet_action may have already run.+ * So double check the sched bit while the takslet+ * is locked before adding it to the list.+ */+ if (test_bit(TASKLET_STATE_SCHED, &t->state)) {+ t->next = NULL;+ *head->tail = t;+ head->tail = &(t->next);+ raise_softirq_irqoff(nr);+ tasklet_unlock(t);+ } else {+ /* This is subtle. If we hit the corner case above+ * It is possible that we get preempted right here,+ * and another task has successfully called+ * tasklet_schedule(), then this function, and+ * failed on the trylock. Thus we must be sure+ * before releasing the tasklet lock, that the+ * SCHED_BIT is clear. Otherwise the tasklet+ * may get its SCHED_BIT set, but not added to the+ * list+ */+ if (!tasklet_tryunlock(t))+ goto again;+ }+ }+}+ void __tasklet_schedule(struct tasklet_struct *t) { unsigned long flags;