问题现象:
RK3568上,卸载时,会出现以下堆栈打印,崩溃在schedule_bug上,而相同的代码在别的平台上没有这个错误。
问题排查:
卸载驱动时的会触发如下代码:
spin_lock(&lock); //申请锁
...;
kthread_stop(thread); //停止线程
...;
spin_unlock(&lock); //释放锁
在spinlock锁之间调用kthread_stop就会触发这个错误,看堆栈打印可以发现问题是出在schedule接口系统调用上。
跟踪kthread_stop代码,里面会设置KTHREAD_SHOULD_STOP的标志位,然后之前创建的线程里的会判断kthread_should_stop跳出循环从而停止线程,然后再返回,整个过程都是阻塞的。等待线程停止都操作,主要是调用wait_for_completion->wait_for_common->do_wait_for_common,这里面主要是建个循环等待,等的过程中会休眠调用schedule();而因为在kthread_stop之前调用了spin_lock。所以这里就有个矛盾点,spinlock是禁止抢占的,而禁止抢占后又调用了schedule来进行CPU抢占,这本身就是很奇怪的操作。
写了以下两个demo测试,发现左边代码会出现一样的报错,而右边代码正常。
所以至此确定堆栈打印是由禁止抢占后调用schedule()引起。至于再看在别的平台上为什么没有这个错误。查看系统发现RK3568是Linux localhost 4.19.172 #47 SMP PREEMPT Mon Jan 10 19:10:31 CST 2022 aarch64,可抢占的,而不会出问题的系统都是不可抢占的。应该跟内核是否支持抢占有关。
然后再继续定位到堆栈打印的代码kernel\core.c的schedule_debug处
if (unlikely(in_atomic_preempt_off())) {
__schedule_bug(prev);
preempt_count_set(PREEMPT_DISABLED);
}
这里的判断是in_atomic_preempt_off(),这个定义是(preempt_count() != PREEMPT_DISABLE_OFFSET),而PREEMPT_DISABLE_OFFSET的定义,在没打开抢占的内核下,定义为0。preempt_count()也是在内核打开抢占的情况下才会去加减,不然也是0。那么也就是在不支持内核抢占的情况下,schedule_debug里的这个if就永远不会进去打印。
至此问题排查结束。
问题结论:
简单总结一句就是不要在内核支持抢占的情况下,在禁抢占后再调用schedule类的系统调度接口。