0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux内核:soft lockup是由于什么原因导致的呢?

Linux阅码场 来源:Linuxer 2020-09-02 17:26 次阅读

提到soft lockup,大家都不会陌生:

BUG:softlockup-CPU#3stuckfor23s![kworker/332]

这个几乎和panic,oops并列,也是非常难以排查甚至比panic更麻烦。至少panic之后你可以去分析一个静态的尸体,然而soft lockup,那是一个动态的过程,甚至转瞬即逝,自带自愈功能。

那么soft lockup是由于什么原因导致的呢?

几乎没有这方面的文章,能找到的也只有个别的案例分析,所以我想趁着周末降至来写一篇关于soft lockup的通用解释。

首先澄清两个关于soft lockup的误区:

soft lockup并不仅仅是由死循环引起的。

soft lockup并不是说在一段代码里执行了23秒,22秒。

这里简单解释一下上面的两点。

事实上,死循环并不一定会导致soft lockup,比如Linux内核生命周期内的0号进程就是一个死循环,此外很多的内核线程都是死循环。

此外,更难指望一段代码可以执行20多秒,要对现代计算机的速度有所概念。

soft lockup发生的真实场景是:

soft lockup是针对单独CPU而不是整个系统的。

soft lockup指的是发生的CPU上在20秒(默认)中没有发生调度切换。

第一点无须解释,下面重点看第二点。

很显然,只要让一个CPU在20秒左右的时间内都不发生进程切换,就会触发soft lockup,这个“20秒内不切换”就是soft lockup发生的根因!

好了,现在我们来看20秒不切换的场景。

死循环的情况
这是最简单的场景,但细节往往不像看起来那么简单。比如你写了一个死循环在内核中执行,它一定会导致soft lockup吗?

我们来看一个内核死循环:

#include #include static int loop_func(void *arg){ int i = 0; while(!kthread_should_stop()) { i++; } return 0;} struct task_struct *kt;static int __init init_loop(void){ kt = kthread_run(loop_func, NULL, "loop_thread"); if (IS_ERR(kt)) { return -1; } return 0;} static void __exit exit_test(void){ kthread_stop(kt);} module_init(init_loop);module_exit(exit_loop);MODULE_LICENSE("GPL");

加载这个模块,会soft lockup吗?

我们知道,虽然loop thread是一个死循环,但是它看起来正如一个普通用户态进程一样,在执行i++循环的时候,其实是可以被其它task抢占掉的,这是最基本的进程调度的常识。

但是如果你真的去加载这个模块,你会发现在有些机器上,它确实会soft lockup,但有的机器上不会,这又是为什么?

这里的关键在于内核抢占。你看下自己系统内核的配置文件,如果下面的配置打开,意味着上述模块的死循环不会造成soft lockup:

CONFIG_PREEMPT=y

如果这个配置没有开,那么便刑不上内核了,因为它在内核态执行,所以没有谁可以抢占它,进而发生soft lockup。

我们对上述的死循环代码是否会触发soft lockup已经很明确了,下面我们看另一种情况。

如果死循环不在内核线程上下文,而是在软中断上下文,会怎样?

很显然,软中断不能被进程抢占,所以一定会soft lockup。

当然,如果真的发生了死循环导致的soft lockup,那肯定是在一个循环代码中执行超过20秒了,不说20秒,如果无人干涉,200000秒都是有的…

现在我们来看另一种复杂的情况,即timer的情况。在讨论timer时,我假设系统的内核抢占是开启的,这样更容易分类讨论,否则,如果关闭了内核抢占,那么事情会变得更加严重。

timer的情况

我们先看下面的timer回调函数:

static void timer_func(unsigned long data){ mdelay(1); mod_timer(&timer, jiffies + 200);}

仅仅执行1ms的函数,它会导致超过20秒不调度切换的soft lockup吗?

初看,应该不会,但是如果我们详细看了Linux内核timer的执行原理,就会明白:

pending在一个CPU上的所有过期timer是顺序遍历执行的。

一轮timer的顺序遍历执行是持有自旋锁的。

这意味着在执行一轮过期timer的过程中,watchdog实时线程将无法被调度从而喂狗,这意味着:

同一CPU上的过期timer积累到一定量,其回调函数的延时之和大于20秒,将会soft lockup。

我们需要进一步了解一下Linux timer的工作机制。

可以把timer的执行过程抽象成下面的逻辑:

run_timers(){ while (now > base.early_jiffies) { for_each_timer(timer, base.list) { detach_timer(timer) forward_early_jiffies(base) call_timer_fn(timer) } }}

很简单的流程,内核把当前过期的timer执行到结束。run_timers可以在软中断上下文中执行,也可以在softirqd内核线程上下文中执行,为了营造soft lockup,我们假设它是在时钟中断退出时的软中断上下文中执行的(记住之前还有个假设,即系统是开启内核抢占的!),此时,run_timers不能被watchdog抢占。

如果一个timer中耗时1ms,那么一个循环需要20000个timer遍历执行,才能凑齐20秒的不能被抢占的时间,进而引发soft lockup。我的天,20000个timer,不可思议!

其实根本就不需要20000个timer,200个足矣!

问题就出现在call_timer_fn,它实际上是调用该timer回调函数的封装!我们知道,timer回调函数中执行了mod_timer的操作,它的逻辑如下:

mod_timer(timer, expires){ list_add_timer(timer, expires, base.list)}

它事实上是把timer又插回了list,如果我们把这个list看作是一条时间线的话,它事实上只是往后移了expires这么远的距离:

假设所有timer的expire都是固定的常量,如果:

我们的timer的足够多,多到按照其expires重新requeue时恰好能填补中间的那段空隙。

我们的timer回调函数耗时恰好和timer的expires流逝速率相一致。

那么,两个甚至多个batch就合并成了一个batch,这意味着一轮timer的执行将不会结束!

我们来试一下:

#include #include #include static int stop = 1; // timer的数量static int size = 1;module_param(size, int, 0644);MODULE_PARM_DESC(size, "size"); // timer的expiresstatic int interval = 200;module_param(interval, int, 0644);MODULE_PARM_DESC(interval, ""); // 回调函数耗时static int dt = 100;module_param(dt, int, 0644);MODULE_PARM_DESC(dt, ""); struct wrapper { struct timer_list timer; spinlock_t lock;}; struct wrapper *wr; static void timer_func(unsigned long data){ int i = data; struct wrapper *w = &wr[i]; spin_lock_bh(&(w->lock)); if (stop == 0) { udelay(dt); // 以忙等模拟耗时 } spin_unlock_bh(&(w->lock)); w->timer.data = i; if (stop == 0) { mod_timer(&(w->timer), jiffies + interval); }} static int __init maint_init(void){ int i; wr = (struct wrapper *)kzalloc(size*sizeof(struct wrapper), GFP_KERNEL); for (i = 0; i < size; i++) { struct wrapper *w = &wr[i]; spin_lock_init(&(w->lock)); init_timer(&(w->timer)); w->timer.expires = jiffies + 20; w->timer.function = timer_func; w->timer.data = i; add_timer(&(w->timer)); } stop = 0; return 0;} static void __exit maint_exit(void){ int i; stop = 1; udelay(100); for (i = 0; i < size; i++) { struct wrapper *w = &wr[i]; del_timer_sync(&(w->timer)); } kfree(wr); } module_init(maint_init);module_exit(maint_exit);MODULE_LICENSE("GPL");

我的测试虚拟机HZ为1000,这意味1ms将会产生一次时钟中断,我们以每个timer函数持锁执行1ms,一共400个timer来加载模块,看下结果:


单核跑满,这意味着timer已经拼接成龙,20秒后,我们将看到soft lockup:


事实上,每个timer回调函数delay 800us,一共200个timer即可触发soft lockup!使用这个代码,你基本可以确定你要测试的机器的timer执行时间的安全阈值。

这就是timer导致的soft lockup的动力学。

关于HZ1000
1ms间隔的时钟中断对于服务器而言是悲哀的,1ms的时间无法容纳太多的timer,也不允许每个timer中有哪怕稍微的合理耗时,1ms一次中断很容易触发run_timers在软中断上下文中被执行,但很遗憾,这就是事实。

抛开timer不谈,HZ1000更多的意义在于快速响应事件而不是增加系统吞吐,这对服务器的单机性能是有伤害的!

说了这么多,现在让我们考虑一下现实。

除了不要在内核中写死循环之外,我们也不应该让timer回调函数执行过久,特别是系统中timer特别多,且expires特别短的情况下。

回到现实中,我们来看一个实例。

假设你使用的内核版本还不支持TCP的lockless listener,那么我们特别要注意一个函数,即inet_csk_reqsk_queue_prune:

这是一个在TCP的per listener的timer中执行的函数。

这个函数的实现采用两层循环,循环耗时取决于:

外层循环:该listener的backlog大小,受程序配置控制。

内层循环:该listener的半连接队列的大小,受系统快照控制。

如果系统中的listener特别多,在收到SYN扫描攻击时,特别容易陷入soft lockup的深渊!幸运的是,这个问题已经在TCP lockless listener的版本中修了,它的效果如下:

将per listener的半连接队列timer换成了per request timer,减少了回调函数处理耗时。

per request timer增加了timer的数量,会不会抵消缩短回调耗时带来的收益,需要攻击来验证。

我们看一个相关issue和patch:
https://patchwork.ozlabs.org/patch/452426/

好了,再次回到核心主题。

触发soft lockup的当然不止死循环和timer,我只是用这两个来说明soft lockup的动力学,即超过2倍的kernel.watchdog_thresh时间不能进行进程调度,就会触发soft lockup告警。至于说stuck for 23s!那只是表象,并不是如其字面表达的那样,23秒的时间在执行一段代码。

此外,频繁的spinlock,rwlock也会导致soft lockup,我这有一个关于IPv6路由查询机制的实例,详情参见:
https://blog.csdn.net/dog250/article/details/91046131

总之,所有的情况将不胜枚举,也不可能通过一篇文章来展示,所以说,遇到此类问题,还是要有一个明确的排查思路或者说范式,才能快速定位问题的根因并且解决之。

当然了,经理并不关注这些烂八七糟的东西。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10824

    浏览量

    211129
  • Linux
    +关注

    关注

    87

    文章

    11225

    浏览量

    208908

原文标题:Linux内核为什么会发生soft lockup?

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    ADS1256IDBR出现转换失败,没有响应的问题,请问是什么原因导致

    您好,ADS1256IDBR出现转换失败,没有响应的问题,请问是什么原因导致
    发表于 11-19 07:35

    电芯抽芯是什么原因导致

    电芯抽芯,通常指的是电池电芯在制造、使用或存储过程中,由于各种原因导致电芯内部结构发生变形或损伤,从而使得电芯内部的活性材料与集流体分离,最终导致电芯性能下降或失效的现象。这种现象在锂
    的头像 发表于 09-24 09:36 267次阅读

    运放输出失真是什么原因导致的?

    下图为原理图: 当按下按键时,测试三极管集电极的波形如下: 若把R27改为10K后,就不会除了上图红色方框的失真现象。这是由什么原因导致
    发表于 09-14 08:03

    什么原因导致压力传感器漂移?

    什么原因导致压力传感器漂移的?我们在设计的时候怎么才能消除压力传感器漂移
    的头像 发表于 08-22 18:00 925次阅读
    <b class='flag-5'>什么原因</b><b class='flag-5'>导致</b>压力传感器漂移?

    程序跑到H723ZGT6的flash擦除那一段命令就死机,什么原因导致

    除了死机外,keil弹出对话框:Cannot access target .Shutting down debug session。 请问这个什么原因导致
    发表于 08-14 08:09

    OPA388低频振荡是什么原因导致的?

    采样转换。 目前遇到的问题是,可能是由于输入分压电阻太大, 导致经过OPA缓冲后,会有一个大概频率为0.05的低频振荡,请教大家,这是什么原因导致的,又如何避免次问题。
    发表于 08-09 06:37

    INA826检测时出现较大幅度偏移,导致结果偏大或偏小是什么原因导致

    电池化成产品上使用许多INA826,用于电池通道电流检测。目前发现INA826检测时出现较大幅度偏移,导致结果偏大或偏小,而且检测通道不固定。请问一下,是什么原因导致
    发表于 08-02 07:35

    导致NMEA2000插头针座变形的原因

    德索工程师说道在NMEA2000插头针座的使用过程中,我们可能会遇到变形的问题。这种问题不仅会影响设备的正常使用,还可能对设备造成损害。那么,是什么原因导致了NMEA2000插头针座的变形
    的头像 发表于 07-01 16:56 221次阅读
    <b class='flag-5'>导致</b>NMEA2000插头针座变形的<b class='flag-5'>原因</b>

    OTA失败err=0x1503是什么原因导致的?

    请问可能是什么原因导致
    发表于 06-19 07:04

    使用cubeprogrammer烧录时,报错Timeout error occured while waiting for acknowledgement是什么原因导致

    在使用cubeprogrammer烧录时(基于UART的),报错Timeout error occured while waiting for acknowledgement.是什么原因导致
    发表于 03-25 06:58

    KEIL调试STM32在运行在打断点位置后芯片复位是什么原因导致

    如题,在用Keil仿真时,在断点满足的地方,芯片复位,有可能是什么原因导致?现象是这样:我运行程序,在某个条件中设下断点,然后等外面条件成立后,按理说程序会停在断点位置,但是实际上芯片被复位了
    发表于 03-18 08:05

    漏电保护开关一用电就跳闸,是什么原因

    漏电保护开关一用电就跳闸,是什么原因? 漏电保护开关是一种用于检测和防止电流漏出的安全装置。当电线或电器出现漏电时,漏电保护开关会立即切断电源,以防止电击事故的发生。然而,如果漏电保护开关一用
    的头像 发表于 02-18 18:11 2405次阅读

    冬季风暴导致Linus Torvalds暂停Linux 6.8内核开发

    Linus Torvalds 在内核邮件列表宣布,由于他位于美国俄勒冈州波特兰的居住地遭遇严重冬季风暴,导致网络和电力中断,他所在的波特兰附近地区气温降至 -10°C,因此他不得不暂停 Lin
    的头像 发表于 01-17 11:01 627次阅读
    冬季风暴<b class='flag-5'>导致</b>Linus Torvalds暂停<b class='flag-5'>Linux</b> 6.8<b class='flag-5'>内核</b>开发

    电脑闪屏是什么原因,怎么解决?

    、软件冲突、驱动问题等。下面我们将详细介绍电脑闪屏的原因以及一些解决方法。 首先我们来介绍一下可能引起电脑闪屏的硬件问题。 1. 显示器问题:有时候电脑闪屏是由于显示器本身的故障导致的。例如,显示屏幕损坏、接口松动或
    的头像 发表于 12-25 11:21 6477次阅读

    什么原因导致磁饱和?如何计算当磁环饱和的电流

    什么原因导致磁饱和?如何计算当磁环饱和的电流? 磁饱和是指在一定的磁场强度下,材料的磁化程度达到了最大值,无论磁场强度如何增加,材料的磁化程度都不会再增加。这种现象主要是
    的头像 发表于 11-28 17:29 4923次阅读