当前位置: 首页 > Linux

故障分析:看Linux如何判断系统“死机”

时间:2023-04-06 19:39:11 Linux

故障分析:看看Linux是如何判断系统“死机”的对于用户态的程序,比较容易排错,但是一旦程序在内核态挂了或者操作系统本身挂了,kill信号甚至硬件中断都无法响应。这个时候,我们能做的就是重启。但是,重启并不能解决根本问题。更麻烦的是,在这个案例中我们几乎得不到任何有用的信息,后续原因的分析和排查更是困难重重。幸运的是,Linux内核已经提供了一系列的机制来帮助我们分析此类问题。让我们来看看如何配置和使用这些机制及其实现原理。UninterruptibleSleep的概念第一个常见的现象是进程长时间处于D(uninterruptiblesleep)状态。依赖它的进程也会阻塞等待它。那么什么是D状态?顾名思义:首先是睡眠状态,也就是说处于这个状态的进程不会消耗CPU。其次,睡眠的原因是因为等待某些资源(比如锁或者磁盘IO),这也是我们看到很多D状态的进程都在处理IO操作的原因。最后一点是不能被打断。这种中断,与“硬件中断”不同,是指在获取资源或超时之前不希望终止。所以他不会被信号唤醒,不会响应kill-9等信号。这也是它和S(interruptiblesleep)状态的区别。进程进入D状态发生在内核代码或底层驱动代码中,典型场景是与硬件通信。正常情况下,我们可以使用top命令观察进程快速进入和退出D状态,但是当出现硬件故障或者驱动程序bug时,进程会长时间处于D状态无法退出,其他依赖或等待它的进程被阻塞。卡住。D状态进程机制内核通常会创建一个khungtaskd守护进程,它会周期性地检查所有进程的状态和上下文切换,然后判断是否有进程长期处于D状态。我们可以通过配置如下内核参数来控制检测超时、告警打印、是否触发panic,以帮助后续分析问题。#超时kernel.hung_task_timeout_secs=120#打印的警告数kernel.hung_task_warnings=10#是否触发系统panickernel.hung_task_panic=0#检测到的最大进程数,当系统进程数超过这个值时,超出部分将被忽略kernel.hung_task_check_count=4194304进程处于D状态时超时打印实现khungtaskd对应的代码在hung_task.c中,主要实现逻辑:每隔一段时间(hung_task_timeout_secs定义的超时时间),检查系统中所有进程是否有处于D状态的进程,记录并检查上下文切换的次数。如果上次记录的上下文切换次数相同,则说明该进程在超时时间内一直处于D状态。根据配置,选择打印告警并触发系统panicstaticint__inithung_task_init(void){......watchdog_task=kthread_run(watchdog,NULL,"khungtaskd");//createkhungtaskdprocess......}staticintwatchdog(void*dummy){......for(;;){while(schedule_timeout_interruptible(timeout_jiffies(timeout)))//休眠一段时间超时=sysctl_hung_task_timeout_secs;......taibunskinterrupttime/check_hung_)开始检查进程//计算上下文切换次数......if(switch_count!=t->last_switch_count){//与上次切换次数比较t->last_switch_count=switch_count;返回;}......printk(KERN_ERR"INFO:task%s:%dblockedformorethan"//Printalarm"%ldseconds.\n",t->comm,t->pid,timeout);printk(KERN_ERR""回显0>/proc/sys/kernel/hung_task_timeout_secs"""禁用此消息。\n");......如果(sysctl_hung_task_panic){trigger_all_cpu_backtrace();panic("hung_task/"blocked)Systempanic}}软锁和硬锁的概念另一种常见的情况是:一个进程一直在占用CPU,其他进程无法调度执行。在极端情况下,它甚至无法响应中断。这个时候系统可能会完全挂起这种不响应任何用户操作的情况还是会出现在内核代码或者驱动代码的bug中。应用程序不会出现这个问题,就像我们写一个死序列程序一样,不会导致系统挂掉。要了解这种情况,首先要了解Linux是抢占式内核。进程可以互相抢占对方的CPU。其次,Linux会为每个CPU核心设置一个固定周期的时钟中断。这个中断是一个非常重要的抢占机会。时钟中断处理程序将决定接下来哪个进程需要抢占CPU。用户态进程执行一段时间后,触发时钟中断,进程调度算法(例如:CFS)可能会将CPU分配给其他进程,使该进程不会一直占用CPU。内核模式下的进程是不同的。首先,它可以屏蔽中断响应,直接消除了抢占的机会。其次,它还可以显式禁用抢占。同时,如果是内核进程,其优先级高于普通进程。而且调度策略也和CFS不同。以上几种情况,如果不主动让出CPU,其他进程就无法执行,最终就会出问题。上面描述的现象在Linux中调用:软锁(soft_lockup)和硬锁(hard_lockup)soft_lockupCPU被某个进程长期占用,其他进程无法调用。例如:long-termdisabledkernelpreemptionsoft_lockupalarmprinthard_lockupCPU被一个进程长期占用,其他进程无法调用,不响应中断。例如:长时间屏蔽中断响应hard_lockup告警打印机制?Linux内核通过watchdog机制检查系统中是否出现soft_lockup和hard_lockup。watchdog的主要思想是:通过高优先级的任务来观察低优先级的任务(进程/中断)是否被成功调度,所以可以通过中断来观察进程是否被正常调度,而通过NMI(非-maskableInterrupt)观察中断是否响应。\我们可以通过以下内核参数来配置检查条件以及是否触发panic,以帮助后续分析问题。#启用watchdogkernel.watchdog=1#hardlockup超时,softlockup超时=2*watchdog_threshkernel.watchdog_thresh=10#是否触发panikernel.hardlockup_panic=1kernel.softlockup_panic=1watchdog的实现对应的代码在watchdog.c中,主要是实现逻辑:为每个CPU核心创建一个(watchdog/%u)内核进程,它会周期性地更新watchdog_touch_ts变量来设置一个时钟中断,它会周期性地更新hrtimer_interrupts变量\同时,它负责检测soft_lockup,通过检查watchdog_touch_ts的值是否被更新来判断(watchdog/%u)进程是否被执行,从而判断CPU是否已经被其他进程占用。设置一个NMI(不可屏蔽中断),每隔watchdog_thresh秒会触发一次\同时负责检测hard_lockup,通过查看hrtimer_interrupts的值是否更新来判断hard_lockup是否对应内核代码#watchdog.cstaticintwatchdog_enable_all_cpus(void){......if(!watchdog_running){err=smpboot_register_percpu_thread(&watchdog_threads);//创建(watchdog/%u)内核进程......}staticvoid__touch_watchdog(void){__this_cpu_write(watchdog_touch_ts,get_timestamp());//更新watchdog_touch_ts}staticvoidwatchdog_enable(unsignedintcpu){......hrtimer->function=watchdog_timer_fn;//设置时钟中断watchdog_nmi_ena蓝牙(中央处理器);//设置NMI(不可屏蔽中断)hrtimer_start(hrtimer,ns_to_ktime(sample_period),HRTIMER_MODE_REL_PINNED);//启动时钟中断}staticenumhrtimer_restartwatchdog_timer_fn(structhrtimer*hrtimer){......watchdog_interrupt_count{unsignedlonghrint=__this_cpu_read(hrtimer_interrupts);如果(__this_cpu_read(hrtimer_interrupts_saved)==hrint)返回真;__this_cpu_write(hrtimer_interrupts_saved,hrint);返回假;以及相关背景和原理在遇到这些情况时,我们可以更快地判断出问题的基本原因和可能发生的地方。同时也介绍了Linux内核提供的一些机制来帮助我们查看和收集必要的日志和信息。有了这些信息,我们就可以通过分析日志,使用kdump等工具进一步排查问题的最终原因。以上代码来自Redhat-7.5,内核版本:linux-3.10.0-862.el7