softlockup和hardlockup介绍

编程

在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变量在时钟中断处理函数里有没有被更新。

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

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

原文链接:https://www.cnblogs.com/linhaostudy/archive/2020/06/15/13130549.html

以上是 softlockup和hardlockup介绍 的全部内容, 来源链接: utcz.com/z/517468.html

回到顶部