​更多内核安全、eBPF分析和实践文章,请关注博客和公众号:

CSDN博客:内核功守道
公众号: 内核功守道

背景

从内核稳定性问题的角度来看内核安全,是基础,也是必备技能。很多时候,一个内核稳定性问题,就是造成系统安全的罪魁祸首。

比如一个简单的内存泄漏问题,前期可能会因为内核泄漏,剩余内存不足导致系统卡顿,此时工程师查找的是Performance性能问题,接下来当系统不能及时回收内存,或者阈值设置不合理,一些进程会被Kill掉,甚至极端情况下,系统会oops或者panic,此时系统展现出来的是Stability稳定性问题。然后如果这个问题触发点很隐蔽,程序发布之前都没有触发而被发现,那后续被黑客利用起来做进一步攻击行为,造成安全漏洞和经济损失,这就是最严重的Security安全问题。

当出现异常死锁、Hang up、死机等问题时,watchdog的作用就很好的体现出来。Watchdog主要用于监测系统运行情况,一旦出现以上异常情况,就会重启系统,并收集crash dump(程序崩溃时保存的运行数据)。

工作流程

Watchdog工作原理:

  1. 假定某一变量的状态能表征系统运行状态,比如中断次数(如果高优先级中断没有发生,就认为CPU卡死),比如/dev/watchdog时间戳(如果超时时间到了仍没有向watchdog节点写数据,就认为用户空间卡死)。
  2. 启动一个watchdog程序,定期观测该变量,来判定系统是否正常,并采取相应动作。
  3. 内核态watchdog主要用于检测内核Lockup。所谓的Lockup,是指某段内核代码一直占着CPU不放,此时内核调度器无法进行调度工作。进一步严重情况下,会导致整个系统卡死。
  4. Lockup涉及到内核线程、时钟中断。它们有不一样的优先级:内核线程 > 时钟中断 。其中,内核线程可以被调度或被中断打断。

详解Lockup

  • 只有内核代码才能引起lockup,因为用户代码是可以被抢占的,只有一种情况例外,就是SCHED_FIFO( 一直运行,直到进程运行完毕才会释放CPU)优先级为99的实时进程。当它被阻塞或被更高优先级进程抢占时,也可能使[watchdog/x]内核线程抢不到CPU而形成soft lockup。
  • 内核代码必须处于禁止内核抢占的状态(preemption disabled),Linux是可抢占的,只在某些特定的代码区才禁止抢占(例如spinlock),才可能形成lockup。

Lockup分为两种:soft lockup 和 hard lockup

  • Soft lockup在CPU无法正常调度其他线程时发生,即某段代码一直占用某个CPU,导致watchdog/x内核线程得不到调度,此时中断仍可响应;
  • Hard lockup在中断无法正常响应时发生,即关中断时间过长或中断处理程序执行时间过长。

Soft lockup详解:

在驱动中加入以下代码可触发soft lockup,通过spinlock()实现关抢占,使得该CPU上的[watchdog/x]线程无法被调度。

static spinlock_t spinlock;
spin_lock_init(&spinlock);
spin_lock(&spinlock);
while(1);
  • 首先给每个CPU开启一个定时(每隔4s执行一次)执行的优先级最高(prio为99)的SCHED_FIFO线程,拥有优先运行的特权)内核线程[watchdog/x],该内核线程会对变量watchdog_touch_ts加加操作,即喂狗。
  • 然后给每个CPU分配一个高精度hrtimer,该定时器的中断服务程序会每隔4s(sample_period seconds (4 seconds by default))检测一下变量watchdog_touch_ts是否被更新过,如果20s内该变量仍未更新,就说明CPU卡住,导致watchdog线程无法调度。

hrtimer的中断处理函数是:kernel/watchdog.c/watchdog_timer_fn()。

中断处理函数主要做了以下事情:

  1. 对变量hrtimer_interrupts加加操作,该变量同时供hard lockup detector用于判断CPU是否响应中断。
  2. 唤醒[watchdog/x]内核线程(对hrtimer_interrupts进行加加操作,就是在唤醒喂狗线程)。检测变量watchdog_touch_ts是否被更新,如果超过20s未更新,说明[watchdog/x]未得到运行,发生了soft lockup,CPU被霸占。

注意,这里的内核线程[watchdog/x]的目的是操作变量watchdog_touch_ts,该变量是被watch的对象。而真正的看门狗,则是由hrtimer中断触发的,即 watchdog_timer_fn()函数,该函数会唤醒喂狗线程。[watchdog/x]是被scheduler调度执行的,而watchdog_timer_fn()则是被中断触发的。

流程图

源码分析

以kernel4.9为例:

1、注册watchdog线程

static struct smp_hotplug_thread watchdog_threads = {.store          = &softlockup_watchdog,.thread_should_run  = watchdog_should_run,.thread_fn      = watchdog,    /* watchdog线程函数 */.thread_comm        = "watchdog/%u",.setup          = watchdog_enable,.cleanup        = watchdog_cleanup,.park           = watchdog_disable,.unpark         = watchdog_enable,
};void __init lockup_detector_init(void)
{/* 注册watchdog线程 */smpboot_register_percpu_thread_cpumask(&watchdog_threads,&watchdog_cpumask);
}smpboot_register_percpu_thread_cpumask(&watchdog_threads,&watchdog_cpumask)
{for_each_online_cpu(cpu) {/* 遍历CPU,为每一个CPU创建watchdog线程 */__smpboot_create_thread(plug_thread, cpu);
}
}__smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
{/* 在特定的CPU上创建线程,回调函数是smpboot_thread_fn */kthread_create_on_cpu(smpboot_thread_fn, td, cpu,ht->thread_comm);
}/* 再看smpboot_thread_fn回调函数 */
static int smpboot_thread_fn(void *data)
{while (1) {switch (td->status) {case HP_THREAD_NONE:__set_current_state(TASK_RUNNING);preempt_enable();if (ht->setup)   /* 使能hard lockup检测 */ht->setup(td->cpu);   /*.setup = watchdog_enable , 创建hrtimer的函数*/td->status = HP_THREAD_ACTIVE;continue;case HP_THREAD_PARKED:__set_current_state(TASK_RUNNING);preempt_enable();if (ht->unpark)ht->unpark(td->cpu);td->status = HP_THREAD_ACTIVE;continue;}
/* 判断当前CPU是否需要执行watchdog线程 */
if (!ht->thread_should_run(td->cpu)) {preempt_enable_no_resched();schedule();} else {__set_current_state(TASK_RUNNING);preempt_enable();/* 核心调用watchdog_threads的.thread_fn    = watchdog *//* watchdog线程函数 */ht->thread_fn(td->cpu);}
}

2、通过以上分析可知,watchdog线程什么时候会被执行,得看thread_should_run函数。

static int watchdog_should_run(unsigned int cpu)
{/* 当hrtimer_interrupts!= soft_lockup_hrtimer_cnt时 *//* watchdog函数会执行一次,watchdog线程会更新watchdog_touch_ts *//* 变量soft_lockup_hrtimer_cnt在watchdog线程函数中会被更新 */return __this_cpu_read(hrtimer_interrupts) !=__this_cpu_read(soft_lockup_hrtimer_cnt);
}

3、更新watchdog_touch_ts

static void __touch_watchdog(void)
{__this_cpu_write(watchdog_touch_ts, get_timestamp());
}static void watchdog(unsigned int cpu)
{/* 将hrtimer_interrupts的值写到soft_lockup_hrtimer_cnt中 *//*在watchdog_should_run中会判断两者是否相等,若不等就执行watchdog线程 */__this_cpu_write(soft_lockup_hrtimer_cnt,__this_cpu_read(hrtimer_interrupts));__touch_watchdog();
}

4、绑定hrtimer中断处理函数,watchdog_timer_fn是hrtimer的中断处理函数,以下是中断函数的注册过程:为每个CPU创建watchdog线程时会调用watchdog_enable函数。

static void watchdog_enable(unsigned int cpu)
{struct hrtimer *hrtimer = &__raw_get_cpu_var(watchdog_hrtimer);/* 启动定时器 */hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);/* 绑定中断处理函数 */hrtimer->function = watchdog_timer_fn;/* 启动hrtimer,timeout 为 4s(4s触发一次hrtimer中断)*/hrtimer_start(hrtimer, ns_to_ktime(sample_period), HRTIMER_MODE_REL_PINNED);
}

5、watchdog_timer_fn函数实体

/* 每隔4s会执行一次 */
static enum hrtimer_restart watchdog_timer_fn(struct hrtimer *hrtimer)
{unsigned long touch_ts = __this_cpu_read(watchdog_touch_ts);int duration;/* 对hrtimer_interrupts加1操作 */watchdog_interrupt_count();duration = is_softlockup(touch_ts);if (unlikely(duration)) {if (softlockup_panic)panic("softlockup: hung tasks");__this_cpu_write(soft_watchdog_warn, true);} else__this_cpu_write(soft_watchdog_warn, false);return HRTIMER_RESTART;
}

该函数做了两件事情:
① 更新hrtimer_interrupts变量(唤醒watchdog线程)。

static void watchdog_interrupt_count(void)
{ __this_cpu_inc(hrtimer_interrupts);
}

之前创建的watchdog线程多久执行一次,和hrtimer_interrupts的值有关系。

static int watchdog_should_run(unsigned int cpu)
{return __this_cpu_read(hrtimer_interrupts) !=__this_cpu_read(soft_lockup_hrtimer_cnt);
}

② 判断是否有soft lockup发生。

static int is_softlockup(unsigned long touch_ts)
{unsigned long now = get_timestamp();/* 如果某个CPU卡死,该CPU的watchdog线程不会被调度,即watchdog_touch_ts *//* 不会被更新。如果20s内都没更新watchdog_touch_ts ,就认为出现了soft lockup *//* get_softlockup_thresh()函数返回20 */if (time_after(now, touch_ts + get_softlockup_thresh()))return now - touch_ts;return 0;
}

问题分析思路:

Soft lockup相关log:

BUG: soft lockup – CPU#2 stuck for 21s!

上述Log说明有进程/线程持续执行的时间超过21s,导致其他进程/线程无法调度,以下情况为形成Soft lockup的主要原因:

  1. 存在死循环( for循环的退出条件弄错)。
  2. 不正确使用spinlock,导致了死锁(譬如spinlock嵌套调用,若顺序不对的话就可能导致死锁)。

在处理该类问题时,可以遵循以下原则:

  • 查看watchdog_touch_ts变量在最近20秒(watchdog_thresh * 2)内,是否被watchdog 线程更新过。若没有更新,就意味着watchdog线程得不到调度。很有可能某个cpu关抢占或中断执行时间过长,导致调度器无法调度watchdog线程。

  • 这种情况下,系统往往不会死掉,但是会很慢。如果将内核参数 softlockup_panic(CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC宏)设置为1,系统会panic。否则,只将warning信息打印出来。

内核配置

  • 开启soft lockup(默认为y)
CONFIG_LOCKUP_DETECTOR
  • 出现softlockup时,使能系统panic(默认为n)
CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC
  • 发生soft lockup时,系统默认会打印相关warning信息。
  • 如果需要抛出panic,也可以在应用层做以下设置:
echo 1 > /proc/sys/kernel/softlockup_panic
cat  /proc/sys/kernel/watchdog_thresh       /*默认10s*/
  • watchdog_thresh默认是10s,如果(watchdog_thresh*2)秒内,watchdog_touch_ts未更新,kernel将panic。最大能设到60s,即120s内watchdog_touch_ts未更新,kernel将panic。

结论

Watchdog是内核最常见也是最容易让人忽略的模块功能,从这一个简单的模块展开,就可以深入接触到内核进度调度、锁机制、死锁处理等内核的核心要点。本篇先从Soft lockup开始分析,结合代码分析流程,并传授处理该类问题的解决思路。此类也是我做内核稳定性时期遇到做多类型的问题,现在再从内核安全的角度来看这类问题,会有另外一种领悟,也同时将这些分享给大家。

下篇将从Hard lockup这类更加棘手的问题点出发,深入问题的核心,刨析问题本质,使大家再遇到Hard lockup时不再恐惧和害怕,能够从容面对并处理。

请记住内核中这个勤劳的监测卫士---Watchdog(Soft lockup篇)相关推荐

  1. 嵌入式Linux驱动开发 02:将驱动程序添加到内核中

    文章目录 目的 基础说明 添加到内核中 Kconfig Makefile 驱动程序 编译与测试 模块方式 编译到内核中 总结 目的 在上一篇文章 <嵌入式Linux驱动开发 01:基础开发与使用 ...

  2. 大话Linux内核中锁机制之原子操作、自旋锁【转】

    转自:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实 ...

  3. 内核中的UDP socket流程(1)

    内核中的UDP socket流程(1)  相对于TCP,UDP协议要简单的多.所以我决定由简入繁,先从UDP协议入手. 前一遍文章已经确定了struct sk_buff被用于socket的接受和发送缓 ...

  4. Linux内核中的vfs,解析 Linux 中的 VFS 文件系统机制

    在Linux系统中,每个分区都是一个文件系统,都有自己的目录层次结构.Linux的最重要特征之一就是支持多种文件系统,这样它更加灵 活,并可以和许多其它种操作系统共存.由于系统已将Linux文件系统的 ...

  5. 电子白板 矢量 编码_当涉及白板编码采访时,请记住准备

    电子白板 矢量 编码 by Andy Tiffany 通过安迪·蒂芙尼(Andy Tiffany) 当涉及白板编码采访时,请记住准备 (When it comes to whiteboard codi ...

  6. 详解 ARM64 内核中对 52 位虚拟地址的支持

    当 64 位硬件变得可用之后,处理更大地址空间(大于 232 字节)的需求变得显而易见.现如今一些公司已经提供 64TiB 或更大内存的服务器,x86_64 架构和 arm64 架构现在允许寻址的地址 ...

  7. linux内核中符号地址的获取

    有些内核函数或者内核变量是不导出的,但是确实需要用,那该怎么办?因此寻找内核符号地址就有必要进行一下总结,更有甚,如果想找一条特定的指令,比如movl的指令地址,那更需要投入一些精力!总的来说,我总结 ...

  8. 如何放出Linux内核中的链表大招

    前言 上回,我们说到Linux内核中max()宏的终极奥义,Linux内核链表也不甘示弱,那么接下来,让我们看看Linux内核中的链表大招. 如何放出Linux内核中的链表大招 前言 一.链表简介 ( ...

  9. 如何使用Linux内核中没有被导出的变量或函数

    更多文章目录:点击这里 GitHub地址:https://github.com/ljrkernel Linux 内核为了减少命名空间的污染,并做到正确的信息隐藏,内核提供了管理内核符号可见性的方法.不 ...

最新文章

  1. LeetCode-数组-189. 旋转数组
  2. HJ37 统计每个月兔子的总数
  3. 什么时候需要用到RCC_APB2Periph_AFIO--复用IO时钟的使用
  4. C++的最后一道坎|百万年薪的程序员
  5. PJ Naughter's Freeware Library
  6. 微软发布Azure Storage不可变存储功能的正式版本
  7. xml-apis-ext.jar
  8. java反编译工具_移动app安全测试 - 客户端 - 反编译保护
  9. Kata: 从随机的三字符列表组中恢复秘密字符串
  10. python获取线程返回值_如何从python中的线程获取返回值?
  11. Java集合类之Collection接口学习
  12. php集成环境 linux,linux下搭建php的集成环境
  13. 搭建一个vue项目完整步骤及详细讲解
  14. 高等数学与计算机的关联论文,高等数学改革管理计算机信息论文
  15. 完美世界前三季营收57亿同比降15% 净利14.4亿同比增80%
  16. android TextView 异常换行问题
  17. 平价的蓝牙耳机哪家质量好?学生党公认的五款高品质蓝牙耳机
  18. 【Java工程中出现问题】XXX has been compiled by a more recent version of the Java Runtime
  19. Python 利用Baostock 下载股票代码,写入Mysql数据库
  20. UART串口通讯协议

热门文章

  1. 编译小米2s CyanogenMod 版本遇到的几个问题 (02.26更新)
  2. web网页设计—— 指环王:护戒使者(13页) 电影网页设计 在线电影制作 个人设计web前端大作业
  3. Spring之SpEL
  4. java一个等号_java中等号的涵义
  5. 单体对象 Singleton Object 提供的顶层方法
  6. ASP.NET控件集合
  7. 信息共享的记忆被囊群算法
  8. 信息技术服务标准(ITSS)
  9. 注解与反射 - 反射 - 操作反射
  10. 分支合并-rebase