BUG: scheduling while atomic 分析 and 为什么中断不能睡眠
遇到一個BUG: scheduling while atomic: kworker/0:2/370/0x00000002;看了這篇文章BUG: scheduling while atomic 分析,是因為在原子操作上下文或者中斷上下文進行了調(diào)度引起的。
先看下為什么會打印出這句:
schedule() -> __schedule() -> schedule_debug() static inline void schedule_debug(struct task_struct *prev) { #ifdef CONFIG_SCHED_STACK_END_CHECKif (task_stack_end_corrupted(prev))panic("corrupted stack end detected inside scheduler\n"); #endifif (unlikely(in_atomic_preempt_off())) {__schedule_bug(prev);preempt_count_set(PREEMPT_DISABLED);}rcu_sleep_check();profile_hit(SCHED_PROFILING, __builtin_return_address(0));schedstat_inc(this_rq()->sched_count); }/** Print scheduling while atomic bug:*/ static noinline void __schedule_bug(struct task_struct *prev) {/* Save this before calling printk(), since that will clobber it */unsigned long preempt_disable_ip = get_preempt_disable_ip(current);if (oops_in_progress)return;printk(KERN_ERR "BUG: scheduling while atomic: %s/%d/0x%08x\n",prev->comm, prev->pid, preempt_count());debug_show_held_locks(prev);print_modules();if (irqs_disabled())print_irqtrace_events(prev);if (IS_ENABLED(CONFIG_DEBUG_PREEMPT)&& in_atomic_preempt_off()) {pr_err("Preemption disabled at:");print_ip_sym(preempt_disable_ip);pr_cont("\n");}if (panic_on_warn)panic("scheduling while atomic\n");dump_stack();add_taint(TAINT_WARN, LOCKDEP_STILL_OK); }也就是 if (unlikely(in_atomic_preempt_off())) 這句成立,就會報這個錯誤。
#define in_atomic_preempt_off() (preempt_count() != PREEMPT_DISABLE_OFFSET)preempt_count()? 讀取 preempt_count,這個成員被用來判斷當前進程是否可以被搶占,這個值是一個 task 的 thread info 中的一個成員變量。也就是 preempt_count 被改變不等于 PREEMPT_DISABLE_OFFSET 之后就會報這個錯。可能是代碼調(diào)用 preempt_disable 顯式的禁止了搶占,也可能是處于中斷上下文等。其中 preempt_disable 和 preempt_enable 成對出現(xiàn)是對 preempt_count 進行加一和減一的操作。
那么 preempt_count 在什么情況下會發(fā)生改變呢?
1.原子操作上下文中,比如spin_lock。
spin_lock()->raw_spin_lock()->_raw_spin_lock()->__raw_spin_lock()static inline void __raw_spin_lock(raw_spinlock_t *lock) {preempt_disable();spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); }2.中斷上下文中。
handle_IRQ()->__handle_domain_irq()->irq_enter()->__irq_enter()#define __irq_enter() \do { \account_irq_enter_time(current); \preempt_count_add(HARDIRQ_OFFSET); \trace_hardirq_enter(); \} while (0)硬件中斷來的時候會調(diào)用 handle_IRQ 進中斷處理函數(shù),會調(diào)用到 preempt_count_add(HARDIRQ_OFFSET),這個函數(shù)雖然不是像 preempt_disable 將 preempt_count 加一,但是它過分的是將其加 HARDIRQ_OFFSET(1UL << 16),這個時候顯然是不等于 PREEMPT_DISABLE_OFFSET,如果調(diào)用 schedule 就會報 bug。
這也同時讓我想起之前說為什么中斷不能睡眠,網(wǎng)上多數(shù)分析的原因都是些主觀上的不能睡眠調(diào)度的原因,并沒有從代碼上分析。其中讓我夜不能寐一句說:
2.4內(nèi)核中schedule()函數(shù)本身在進來的時候判斷是否處于中斷上下文:
if(unlikely(in_interrupt()))
BUG();
因此,強行調(diào)用schedule()的結(jié)果就是內(nèi)核BUG,但我看2.6.18的內(nèi)核schedule()的實現(xiàn)卻沒有這句,改掉了。
其實2.6以上是有實現(xiàn)的,就是上面的 flow 觸發(fā)。2.4是分別判斷 in_atomic 和 in_interrupt (咱也沒找到2.4的kernel code,咱就敢說,你找去吧,反正你也找不到),2.6以后將所有不允許調(diào)度的情況都集合在 preempt_count 這個變量上,用不同的位來代表不同的情況,具體preempt_count的數(shù)據(jù)格式可以參考下圖:
preemption count 用來記錄當前被顯式的禁止搶占的次數(shù),這就和代碼里一致了:中斷中是加 HARDIRQ_OFFSET(1UL << 16) 對應(yīng) bit16,原子上下文是加1對應(yīng) bit0。
我們再看那句log:
printk(KERN_ERR "BUG: scheduling while atomic: %s/%d/0x%08x\n",prev->comm, prev->pid, preempt_count());最后會將?preemption count 打印出來,這樣就能通過這個值來看是因為在哪種情況下調(diào)用 schedule 而導(dǎo)致的 kernel crash,還是內(nèi)核牛批啊。
所以說也不能老是聽大佬說中斷不能調(diào)睡眠函數(shù)又不去想為啥,還是得看代碼怎么實現(xiàn)的,它萬一有一天客戶拿槍頂著我的腦袋讓我解釋個中原因呢?
?我賭你槍里沒有子彈!
?
參考文章:
中斷上下文中調(diào)度會怎樣?
linux kernel的中斷子系統(tǒng)之(八):softirq
總結(jié)
以上是生活随笔為你收集整理的BUG: scheduling while atomic 分析 and 为什么中断不能睡眠的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: L2-016 愿天下有情人都是失散多年的
- 下一篇: 牛客练习赛23 托米的咒语