遇到一个BUG: scheduling while atomic: kworker/0:2/370/0x00000002;看了这篇文章
BUG: scheduling while atomic 分析
,是因为在原子操作上下文或者中断上下文进行了调度引起的。
先看下为什么会打印出这句:
schedule() -> __schedule() -> schedule_debug()
static inline void schedule_debug(struct task_struct *prev)
#ifdef CONFIG_SCHED_STACK_END_CHECK
if (task_stack_end_corrupted(prev))
panic("corrupted stack end detected inside scheduler\n");
#endif
if (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 之后就会报这个错。可能是代码调用 preempt_disable 显式的禁止了抢占,也可能是处于中断上下文等。其中 preempt_disable 和 preempt_enable 成对出现是对 preempt_count 进行加一和减一的操作。
那么 preempt_count 在什么情况下会发生改变呢?
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)
硬件中断来的时候会调用 handle_IRQ 进中断处理函数,会调用到 preempt_count_add(HARDIRQ_OFFSET),这个函数虽然不是像 preempt_disable 将 preempt_count 加一,但是它过分的是将其加 HARDIRQ_OFFSET(1UL << 16),这个时候显然是不等于 PREEMPT_DISABLE_OFFSET,如果调用 schedule 就会报 bug。
这也同时让我想起之前说为什么中断不能睡眠,网上多数分析的原因都是些主观上的不能睡眠调度的原因,并没有从代码上分析。其中让我夜不能寐一句说:
2.4内核中schedule()函数本身在进来的时候判断是否处于中断上下文:
if(unlikely(in_interrupt()))
BUG();
因此,强行调用schedule()的结果就是内核BUG,但我看2.6.18的内核schedule()的实现却没有这句,改掉了。
其实2.6以上是有实现的,就是上面的 flow 触发。2.4是分别判断 in_atomic 和 in_interrupt (咱也没找到2.4的kernel code,咱就敢说,你找去吧,反正你也找不到),2.6以后将所有不允许调度的情况都集合在 preempt_count 这个变量上,用不同的位来代表不同的情况,具体preempt_count的数据格式可以参考下图:
preemption count 用来记录当前被显式的禁止抢占的次数,这就和代码里一致了:中断中是加 HARDIRQ_OFFSET(1UL << 16) 对应 bit16,原子上下文是加1对应 bit0。
我们再看那句log:
printk(KERN_ERR "BUG: scheduling while atomic: %s/%d/0x%08x\n",
prev->comm, prev->pid, preempt_count());
最后会将 preemption count 打印出来,这样就能通过这个值来看是因为在哪种情况下调用 schedule 而导致的 kernel crash,还是内核牛批啊。
所以说也不能老是听大佬说中断不能调睡眠函数又不去想为啥,还是得看代码怎么实现的,它万一有一天客户拿枪顶着我的脑袋让我解释个中原因呢?
我赌你枪里没有子弹!
参考文章:
中断上下文中调度会怎样?
linux kernel的中断子系统之(八):softirq
schedule -> __schedule -> schedule_debug
static inline void schedule_debug(struct task_struct *prev)
#ifdef CONFIG_SCHED_STACK_END_CHECK
栈区检查还是别的?看后面的
if (task_stack_end_corrupted(prev))
copy from: http://www.wowotech.net/kernel_synchronization/spinlock.html/comment-page-2#comments作者:linuxer 发布于:2015-4-22 12:22 分类:内核同步机制
在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?如果只
[26578.636839] C1 [ swapper/1] BUG: scheduling while atomic: swapper/1/0/0x00000002
[26578.636869] C0 [ kworker/u:1] CPU1 is up
[26578.636900] C1 [ swapper/1] Modu
linux内核打印"BUG: scheduling while atomic"和"bad: scheduling from the idle thread"错误的时候,通常是在中断处理函数中调用了可以休眠的函数,如semaphore,mutex,sleep之类的可休眠的函数,而linux内核要求在中断处理的时候,不允许系统调度,不允许抢占,要等到中断处理完成才能做其他事情。因此,要充分考虑中断处理...
这个问题实际上是一个老生常谈的问题,答案也很简单,Linux在软中断上下文中是不能睡眠的,原因在于Linux的软中断实现上下文有可能是中断上下文,如果在中断上下文中睡眠,那么会导致Linux无法调度,直接的反应是系统KernelPanic,并且提示dequeue_task出错。所以,在软中断上下文中,我们不能使用信号量等可能导致睡眠的函数,这一点在编写IO回调函数时需要特别注意。在最近的一个项目中
在Linux内核的五大组成部分(进程管理、内存管理、设备驱动、文件系统、网络协议)中,进程管理是非常重要的一部分,它虽然不像内存管理、虚拟文件系统那样复杂,也不像进程间通信那样条理化,但作为五大内核组成部分之一,进程管理对我们理解内核的运作、对于我们今后的编程非常重要。同时,作为五大组成部分的核心,它与其他四个模块都有联系。 因此,对于进程管理是必须要充分理解和掌握的。本文对进程管理进行详细的介绍,首先介绍进程及其相关的概念,而后介绍进程的创建、退出等基本操作以及线程的相关知识。
中断处理的时候,不应该发生进程切换,因为在中断context中,唯一能打断当前中断handler的只有更高优先级的中断,它不会被进程打断(这点对 于softirq,tasklet也一样,因此这些bottom half也不能休眠),如果在中断context中休眠,则没有办法唤醒它,因为所有的 wake_up_xxx都是针对某个进程而言的,而在中断context中,没有进程的概念,没有一个task_...
Linux内核把进程称为任务(task),进程的虚拟地址空间分为用户虚 拟地址空间和内核虚拟地址空间,所有进程共享内核虚拟地址空间,每个 进程有独立的用户空间虚拟地址空间。
进程有两种特殊形式:没有用户虚拟地址空间的进程称为内核线程, 共享用户虚拟地址空间的进程称为用户线程。通用在不会引起混淆的情况下把用户线程简称为线程。共享同一个用户虚拟地址空间的所有用户线程组成一个线程组。
2、Linux进程四要素:
a.有一段程序供其执行。
b.有进程专用的系...