zoukankan      html  css  js  c++  java
  • soft lockup和hard lockup介绍

    在linux kernel里,有一个debug选项LOCKUP_DETECTOR。

    使能它可以打开kernel中的soft lockup和hard lockup探测。

    这两个东西到底有什么用处那?

    首先,soft/hard lockup的实现在kernel/watchdog.c中,

    主体涉及到了3个东西:kernel线程,时钟中断,NMI中断(不可屏蔽中断)。

    这3个东西具有不一样的优先级,依次是kernel线程 < 时钟中断 < NMI中断。

    而正是用到了他们之间优先级的区别,所以才可以调试系统运行中的两种问题:

    1. 抢占被长时间关闭而导致进程无法调度(soft lockup)

    2. 中断被长时间关闭而导致更严重的问题(hard lockup)

    接下来我们从具体代码入手分析linux(3.10)是如何实现这两种lockup的探测的:

    static struct smp_hotplug_thread watchdog_threads = {
    	.store			= &softlockup_watchdog,
    	.thread_should_run	= watchdog_should_run,
    	.thread_fn		= watchdog,
    	.thread_comm		= "watchdog/%u",
    	.setup			= watchdog_enable,
    	.park			= watchdog_disable,
    	.unpark			= watchdog_enable,
    };
     
    void __init lockup_detector_init(void)
    {
    	set_sample_period();
    	if (smpboot_register_percpu_thread(&watchdog_threads)) {
    		pr_err("Failed to create watchdog threads, disabled
    ");
    		watchdog_disabled = -ENODEV;
    	}
    }
    

    首先,系统会为每个cpu core注册一个一般的kernel线程,名字叫watchdog/0, watchdog/1...以此类推。

    这个线程会定期得调用watchdog函数

    static void __touch_watchdog(void)
    {
    	__this_cpu_write(watchdog_touch_ts, get_timestamp());
    }
     
    static void watchdog(unsigned int cpu)
    {
    	__this_cpu_write(soft_lockup_hrtimer_cnt,
    			 __this_cpu_read(hrtimer_interrupts));
    	__touch_watchdog();
    }
    

    我们先不理会这个线程处理函数watchdog多久被调用一次,我们就先简单的认为,这个线程是负责更新watchdog_touch_ts的。

    然后我们要看一下时钟中断了:

    static void watchdog_enable(unsigned int cpu)
    {
    	struct hrtimer *hrtimer = &__raw_get_cpu_var(watchdog_hrtimer);
     
    	/* kick off the timer for the hardlockup detector */
    	hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    	hrtimer->function = watchdog_timer_fn;
     
    	/* done here because hrtimer_start can only pin to smp_processor_id() */
    	hrtimer_start(hrtimer, ns_to_ktime(sample_period),
    		      HRTIMER_MODE_REL_PINNED);
    }
    

    时钟中断处理函数是watchdog_timer_fn

    static enum hrtimer_restart watchdog_timer_fn(struct hrtimer *hrtimer)
    {
    	unsigned long touch_ts = __this_cpu_read(watchdog_touch_ts);
    	int duration;
     
    	/* kick the hardlockup detector */
    	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;
    }
    

    这个函数主要做2件事情:

    1. 更新hrtimer_interrupts变量。
    static void watchdog_interrupt_count(void)
    {
    	__this_cpu_inc(hrtimer_interrupts);
    }
    

    这里我们就要回顾之前创建的那个kernel线程了,多久调用一次就和hrtimer_interrupts的值密切相关。

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

    那就是说,kernel线程和时钟中断函数的频率是相同的。默认情况是10*2/5=4秒一次。

    int __read_mostly watchdog_thresh = 10;
     
    static int get_softlockup_thresh(void)
    {
    	return watchdog_thresh * 2;
    }
     
    static void set_sample_period(void)
    {
    	/*
    	 * convert watchdog_thresh from seconds to ns
    	 * the divide by 5 is to give hrtimer several chances (two
    	 * or three with the current relation between the soft
    	 * and hard thresholds) to increment before the
    	 * hardlockup detector generates a warning
    	 */
    	sample_period = get_softlockup_thresh() * ((u64)NSEC_PER_SEC / 5);
    }
    
    1. 就是要探测是否有soft lockup发生。
    static int is_softlockup(unsigned long touch_ts)
    {
    	unsigned long now = get_timestamp();
     
    	/* Warn about unreasonable delays: */
    	if (time_after(now, touch_ts + get_softlockup_thresh()))
    		return now - touch_ts;
     
    	return 0;
    }
    

    很容易理解,其实就是查看watchdog_touch_ts变量在最近20秒的时间内,有没有被创建的kernel thread更新过。

    假如没有,那就意味着线程得不到调度,所以很有可能就是在某个cpu core上抢占被关闭了,所以调度器没有办法进行调度。

    这种情况下,系统往往不会死掉,但是会很慢。

    有了soft lockup的机制,我们就能尽早的发现这样的问题了。

    分析完soft lockup,我们继续分析hard lockup

    static int watchdog_nmi_enable(unsigned int cpu)
    {
    	struct perf_event_attr *wd_attr;
     
    	wd_attr = &wd_hw_attr;
    	wd_attr->sample_period = hw_nmi_get_sample_period(watchdog_thresh);
     
    	/* Try to register using hardware perf events */
    	event = perf_event_create_kernel_counter(wd_attr, cpu, NULL, watchdog_overflow_callback, NULL);
    }
    

    perf_event_create_kernel_counter函数主要是注册了一个硬件的事件。

    这个硬件在x86里叫performance monitoring,这个硬件有一个功能就是在cpu clock经过了多少个周期后发出一个NMI中断出来。

    u64 hw_nmi_get_sample_period(int watchdog_thresh)
    {
    	return (u64)(cpu_khz) * 1000 * watchdog_thresh;
    }
    

    在这里,根据当前cpu的频率,算出一个值,也就是20秒cpu clock经过的周期数。

    这样一来,当cpu全负荷跑完20秒后,就会有一个NMI中断发出,而这个中断的出路函数就是watchdog_overflow_callback。

    static void watchdog_overflow_callback(struct perf_event *event,
    		 struct perf_sample_data *data,
    		 struct pt_regs *regs)
    {
    	if (is_hardlockup()) {
    		int this_cpu = smp_processor_id();
     
    		if (hardlockup_panic)
    			panic("Watchdog detected hard LOCKUP on cpu %d", this_cpu);
    		else
    			WARN(1, "Watchdog detected hard LOCKUP on cpu %d", this_cpu);
     
    		return;
    	}
     
    	return;
    }
    

    这个函数主要就是调用is_hardlockup

    static int is_hardlockup(void)
    {
    	unsigned long hrint = __this_cpu_read(hrtimer_interrupts);
     
    	if (__this_cpu_read(hrtimer_interrupts_saved) == hrint)
    		return 1;
     
    	__this_cpu_write(hrtimer_interrupts_saved, hrint);
    	return 0;
    }
    

    而这个函数主要就是查看hrtimer_interrupts变量在时钟中断处理函数里有没有被更新。

    假如没有更新,就意味着中断出了问题,可能被错误代码长时间的关中断了。

    那这样,相应的问题也就暴露出来了。

  • 相关阅读:
    UOJ #455 [UER #8]雪灾与外卖 (贪心、模拟费用流)
    Codeforces 482E ELCA (LCT)
    Codeforces 798D Mike and distribution (构造)
    AtCoder AGC017C Snuke and Spells
    HDU 6089 Rikka with Terrorist (线段树)
    HDU 6136 Death Podracing (堆)
    AtCoder AGC032D Rotation Sort (DP)
    jenkins+python+kubectl实现批量更新k8s镜像
    Linux 下载最新kubectl版本的命令:
    jenkins X 和k8s CI/CD
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/13130549.html
Copyright © 2011-2022 走看看