History

sched: don't cause task state changes in nested sleep debugging

Commit 8eb23b9f35aa ("sched: Debug nested sleeps") added code to report on nested sleep conditions, which we generally want to avoid because the inner sleeping operation can re-set the thread state to TASK_RUNNING, but that will then cause the outer sleep loop not actually sleep when it calls schedule. However, that's actually valid traditional behavior, with the inner sleep being some fairly rare case (like taking a sleeping lock that normally doesn't actually need to sleep). And the debug code would actually change the state of the task to TASK_RUNNING internally, which makes that kind of traditional and working code not work at all, because now the nested sleep doesn't just sometimes cause the outer one to not block, but will cause it to happen every time. In particular, it will cause the cardbus kernel daemon (pccardd) to basically busy-loop doing scheduling, converting a laptop into a heater, as reported by Bruno Prémont. But there may be other legacy uses of that nested sleep model in other drivers that are also likely to never get converted to the new model. This fixes both cases: - don't set TASK_RUNNING when the nested condition happens (note: even if WARN_ONCE() only _warns_ once, the return value isn't whether the warning happened, but whether the condition for the warning was true. So despite the warning only happening once, the "if (WARN_ON(..))" would trigger for every nested sleep. - in the cases where we knowingly disable the warning by using "sched_annotate_sleep()", don't change the task state (that is used for all core scheduling decisions), instead use '->task_state_change' that is used for the debugging decision itself. (Credit for the second part of the fix goes to Oleg Nesterov: "Can't we avoid this subtle change in behaviour DEBUG_ATOMIC_SLEEP adds?" with the suggested change to use 'task_state_change' as part of the test)

Linus Torvalds committed 11 years ago

Commit 00845eb1 parent 788807d2 files · +3 −4Browse files

Unified · Split

2 files
  1. include/linux/kernel.h +1 −1
  2. kernel/sched/core.c +2 −3
include/linux/kernel.h+1−1
@@ -176,7 +176,7 @@ extern int _cond_resched(void); · 175 unmodified lines
176176 */
177177 # define might_sleep() \
178178 do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0)
179−# define sched_annotate_sleep() __set_current_state(TASK_RUNNING)
179+# define sched_annotate_sleep() (current->task_state_change = 0)
180180 #else
181181 static inline void ___might_sleep(const char *file, int line,
182182 int preempt_offset) { }
kernel/sched/core.c+2−3
@@ -7292,13 +7292,12 @@ void __might_sleep(const char *file, int line, int preempt_offset) · 7291 unmodified lines
72927292 * since we will exit with TASK_RUNNING make sure we enter with it,
72937293 * otherwise we will destroy state.
72947294 */
7295− if (WARN_ONCE(current->state != TASK_RUNNING,
7295+ WARN_ONCE(current->state != TASK_RUNNING && current->task_state_change,
72967296 "do not call blocking ops when !TASK_RUNNING; "
72977297 "state=%lx set at [<%p>] %pS\n",
72987298 current->state,
72997299 (void *)current->task_state_change,
7300− (void *)current->task_state_change))
7301− __set_current_state(TASK_RUNNING);
7300+ (void *)current->task_state_change);
73027301
73037302 ___might_sleep(file, line, preempt_offset);
73047303 }