在linux kernel裏,有一個debug選項LOCKUP_DETECTOR。linux
使能它能夠打開kernel中的soft lockup和hard lockup探測。ide
這兩個東西到底有什麼用處那?函數
首先,soft/hard lockup的實如今kernel/watchdog.c中,this
主體涉及到了3個東西:kernel線程,時鐘中斷,NMI中斷(不可屏蔽中斷)。線程
這3個東西具備不同的優先級,依次是kernel線程 < 時鐘中斷 < NMI中斷。debug
而正是用到了他們之間優先級的區別,因此才能夠調試系統運行中的兩種問題:調試
搶佔被長時間關閉而致使進程沒法調度(soft lockup)rest
中斷被長時間關閉而致使更嚴重的問題(hard lockup)code
接下來咱們從具體代碼入手分析linux(3.10)是如何實現這兩種lockup的探測的:orm
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\n"); 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件事情:
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); }
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變量在時鐘中斷處理函數裏有沒有被更新。
假如沒有更新,就意味着中斷出了問題,可能被錯誤代碼長時間的關中斷了。
那這樣,相應的問題也就暴露出來了。