[Android稳定性] 第005篇 [问题篇] 原子状态调度引起死机

0. 问题现象

锁的使用不当往往会导致死机异常:
之前碰到过一例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[2021:11:18 15:28:05](1)BUG: scheduling while atomic: Binder:1199_2/5201/0x00000002
[2021:11:18 15:28:05](1)------------[ cut here ]------------
[2021:11:18 15:28:05](1)kernel BUG at kernel/sched/walt/walt_debug.c:16!
[2021:11:18 15:28:05](1)pc : android_rvh_schedule_bug+0x4/0x8 [sched_walt_debug]
[2021:11:18 15:28:05](1)lr : __schedule_bug+0x100/0x138
[2021:11:18 15:28:05](1)Call trace:
[2021:11:18 15:28:05](1) android_rvh_schedule_bug+0x4/0x8 [sched_walt_debug]
[2021:11:18 15:28:05](1) __schedule+0x6a0/0xacc
[2021:11:18 15:28:05](1) schedule+0x68/0x190
[2021:11:18 15:28:05](1) __mutex_lock+0x39c/0x9c8
[2021:11:18 15:28:05](1) __mutex_lock_slowpath+0x18/0x28
[2021:11:18 15:28:05](1) clk_get_parent+0xd0/0x254
[2021:11:18 15:28:05](1) clock_debug_print_enabled_clocks+0xdc/0x268 [clk_qcom]
[2021:11:18 15:28:05](1) clk_debug_suspend_trace_probe+0x68/0x74 [clk_qcom]
[2021:11:18 15:28:05](1) s2idle_enter+0x294/0x40c
[2021:11:18 15:28:05](1) suspend_enter+0x158/0x7c8
[2021:11:18 15:28:05](1) suspend_devices_and_enter+0x134/0x560
[2021:11:18 15:28:05](1) enter_state+0x26c/0x724
[2021:11:18 15:28:05](1) state_store+0x15c/0x1e8
[2021:11:18 15:28:05](1) kobj_attr_store+0x38/0x88
[2021:11:18 15:28:05](1) sysfs_kf_write+0x64/0xc0
[2021:11:18 15:28:05](1) kernfs_fop_write_iter+0x1a4/0x48c
[2021:11:18 15:28:05](1) vfs_write+0x300/0x374
[2021:11:18 15:28:05](1) ksys_write+0x7c/0x150
[2021:11:18 15:28:05](1) __arm64_sys_write+0x20/0x30
[2021:11:18 15:28:05](1) el0_svc_common+0xc4/0x250
[2021:11:18 15:28:05](1) el0_svc+0x38/0x9c
[2021:11:18 15:28:05](1) el0_sync_handler+0x8c/0xf0
[2021:11:18 15:28:05](1) el0_sync+0x1a8/0x1c0

1. 问题分析

其实错误原因log中已经很直白的告诉你了:

BUG: scheduling while atomic: Binder:1199_2/5201/0x00000002 : 在原子状态下执行了调度命令,而原子状态是不允许调度的。

具体原因可以参考这篇文章:Linux中的preempt_count - 知乎

这类问题,其实反而比较容易定位,看调用栈,找到出问题的函数,然后看这个函数附近是不是有mutex_lock,spin_lock之类的操作。

上面这个例子是一个开源社区的人上传的,涉及逻辑比较多,只能找高通的人帮忙反馈了。

2. 另一个案例

下面是一个典型的错误代码,也是报错scheduling while atomic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int onewr_open_ic(void)
{
opt_swr_slk_irqsave();
opt_get_gpio_dir();
opt_gpio_dir_output(HIGH);
opt_swr_slk_irqstore();
return 0;
}

void opt_swr_slk_irqsave(void)
{
if (!g_lock_status) {
spin_lock_irqsave(g_onewr_lock, g_slk_flags);
g_lock_status = true;
}
}
EXPORT_SYMBOL(opt_swr_slk_irqsave);
void opt_swr_slk_irqstore(void)
{
if (g_lock_status) {
spin_unlock_irqrestore(g_onewr_lock, g_slk_flags);
g_lock_status = false;
}
}
EXPORT_SYMBOL(opt_swr_slk_irqstore);

3. 原因分析

当有两个cpu同时执行这个加锁解锁操作的时候,

  1. CPU0 先持锁,CPU1等锁。此时g_lock_status = true
  2. CPU0 释放锁,CPU1持锁。这里就会出问题,是CPU0先把g_lock_status = false,还是CPU1先把g_lock_status = true;
  3. 如果cpu0卡了下,cpu1先设置g_lock_status = true;,然后cpu0才设置g_lock_status = false;那么,当cpu3执行完,准备释放锁的时候,发现g_lock_status = false;就不会执行释放操作。
  4. 紧接着,cpu3准备调度去执行其他线程的时候,就会报错,因为它还持着锁,处于原子状态。

这个问题的解决方法就是把全局变量去掉,完全是多此一举的代码逻辑。因为本来就是成对使用的。

4. 总结

我们要记住,锁就是用来保护临界区的,也可以狭义的理解为,全局变量/资源。
不能反过来用全局变量来控制加解锁,这种大部分都会出问题,特别是现在都是多核的系统,会并发执行的。