printk死锁是指在使用Linux内核中的printk
函数打印日志时,由于某种原因导致系统中两个或多个线程相互等待对方释放资源而无法继续执行的情况。printk死锁会影响系统应用程序和业务的正常运行,最终触发内核宕机,所以预防和及时解决printk死锁对于维持系统稳定性和可靠性至关重要。本文介绍Alibaba Cloud Linux系统出现printk死锁导致系统宕机的原因及解决方案。
问题描述
在使用Alibaba Cloud Linux发生内核宕机时,分析vmcore文件存在以下现象:
当系统发生宕机时,会生成转储文件vmcore,您可以通过查看该文件获取宕机时的内核日志,并根据其中的calltrace信息(通常以"Call Trace:"开头)来定位问题的发生位置,分析宕机原因,从而进行修复和调试。
内核日志(dmesg)存在一些
warning
级别的打印日志,通常与调度(scheduling)、工作队列(work_queue)有关。分析vmcore文件会发现某个进程的calltrace信息有如下特征:
最近的函数是关于获取自旋锁的调用,比如
_raw_spin_lock
、queued_spin_lock_slowpath
、raw_spin_rq_lock_netsted
等。函数调用路径是从
printk
到console_unlock
,再到上述获取自旋锁的调用。
问题原因
系统宕机是由Linux社区的printk机制长期存在死锁的问题导致的,且是已知问题,该问题出现概率较低,但在Alibaba Cloud Linux 3的5.10.134-16.3
内核版本出现该问题的概率偏高。
为什么会出现printk死锁?
内核在持有work_queue(工作队列)或runqueue(运行队列,简称rq)的自旋锁后,如果调用printk函数打印内核日志,printk的底层实现会去调用drm驱动程序,而drm驱动程序由于自身的实现原因又会去尝试对work_queue或rq进行加锁,导致printk死锁,最终导致系统宕机。
说明drm驱动尝试加锁的详细信息,请参考drm/fb-helper: Add fb_deferred_io support补丁。
为什么调度(scheduling)和工作队列(work_queue)会出现
warning
日志?由于内核持有work_queue或rq的自旋锁并使用printk函数打印日志的场景,又通常发生在调度(scheduling)和工作队列(work_queue)实现代码的warning路径上,所以可以观察到内核的dmesg信息中出现调度(scheduling)和工作队列(work_queue)相关的报警信息。
为什么Alibaba Cloud Linux 3的
5.10.134-16.3
内核版本出现该问题的概率偏高?事实上,调度(scheduling)和工作队列(work_queue)打印
warning
日志的情况并不多,但是在Alibaba Cloud Linux 3的5.10.134-16.3
内核版本中,从Linux上游社区回合的异步unthrottle特性存在回归缺陷,增大了触发打印warning
日志的概率,从而导致Alibaba Cloud Linux 3中出现该问题的概率偏高。
影响范围
该问题目前是Linux社区共性问题,且触发该问题的补丁drm/fb-helper: Add fb_deferred_io support是在Linux上游社区的v4.10版本引入。
在Alibaba Cloud Linux的
4.19
和5.10
内核版本存在该问题。在Alibaba Cloud Linux 3的
5.10.134-16.3
内核版本中出现该问题的概率更高。
解决方案
当您发现此类问题时,可以通过以下命令调整printk的打印等级,阻止warning
级别的日志打印到串口的方式避开该问题。
如果您的日志系统是依赖捕获串口输出而非内核日志(dmesg)信息,请谨慎评估后使用。
该方法会导致串口中的
warning
日志缺失,但不会影响内核日志(dmesg)中的warning
日志。
sysctl -w kernel.printk="<console_loglevel> <default_message_loglevel> <minimum_console_loglevel> <default_console_loglevel>" >> /etc/sysctl.conf
其中kernel.printk
的四个值说明如下:
console_loglevel
:表示高于(不含)此优先级的信息会被打印至串口。default_message_loglevel
:如果在printk时未指定打印等级,则默认使用该等级。minimum_console_loglevel
:表示console_loglevel
可以被设置的最小值。default_console_loglevel
:表示console_loglevel
的默认值。
由于Linux将内核的日志打印等级分为8个,等级越低,优先级越高。因此需要阻止warning
级别的日志打印到串口,则console_loglevel
最大设置为4才合理。命令示例如下:
sysctl -w kernel.printk="4 4 1 7" >> /etc/sysctl.conf
日志打印等级说明:
#define LOGLEVEL_EMERG 0 /* system is unusable */
#define LOGLEVEL_ALERT 1 /* action must be taken immediately */
#define LOGLEVEL_CRIT 2 /* critical conditions */
#define LOGLEVEL_ERR 3 /* error conditions */
#define LOGLEVEL_WARNING 4 /* warning conditions */
#define LOGLEVEL_NOTICE 5 /* normal but significant condition */
#define LOGLEVEL_INFO 6 /* informational */
#define LOGLEVEL_DEBUG 7 /* debug-level messages */