重?c網(wǎng)站開發(fā)電腦優(yōu)化是什么意思
問題描述
某 4.x 內(nèi)核使用 lkdtm 測(cè)試異常注入功能時(shí),在觸發(fā) softlockup 后,內(nèi)核一直檢測(cè)不到不能觸發(fā) panic 自動(dòng)重啟。
排查過程
- 排查 softlockup 相關(guān)內(nèi)核配置參數(shù)是否開啟–已經(jīng)開啟
- 排查 sysctl kernel.softlockup_panic 配置是否開啟–已經(jīng)開啟
- 排查 sysctl kernel.panic 配置是否設(shè)置 – 正常設(shè)置
- 排查每個(gè)核上的 watchdog_timer_fn hrtimer 定時(shí)器事件是否開啟
在第四步中,查看 /proc/timer_list 發(fā)現(xiàn)只有 0 核上開啟了 watchdog_timer_fn,其它核上未開啟故而無法檢測(cè)到 softlockup 與 hardlockup。
為什么其它核上未開啟用于 softlockup 檢測(cè)的 hrtimer 定時(shí)器事件?
在定位這個(gè)問題前,筆者對(duì)于 softlockup 的檢測(cè)原理有個(gè)概要的認(rèn)識(shí),知道它會(huì)在每個(gè) cpu 核上創(chuàng)建一個(gè) hrtimer 定時(shí)器來進(jìn)行檢測(cè),既然這個(gè)定時(shí)器都沒有創(chuàng)建,那檢測(cè)不到異常也是正常的。
另外一個(gè)問題就是為什么 0 核上開啟了 watchdog_timer_fn 但是也檢測(cè)不到呢?
在回答這個(gè)問題前筆者先分析下第一個(gè)問題,粗略掃描代碼發(fā)現(xiàn) softlockup 檢測(cè)功能的開啟與 watchdog_cpumask 掩碼有關(guān),此掩碼配置又會(huì)受 housekeeping_mask 掩碼內(nèi)容影響,下面基于 4.x 內(nèi)核對(duì)這一過程進(jìn)行分析。
watchdog_cpumask 相關(guān)代碼分析
內(nèi)核會(huì)在 lockup 檢測(cè)器初始化時(shí),在 NO_HZ_FULL
開啟及 nohz_full
功能運(yùn)行時(shí)將未開啟 nohz_full 的 cpu 核掩碼寫入 housekeeping_mask
,此后 housekeeping_mask
被拷貝到 watchdog_cpumask 中,否則拷貝 cpu_possible_mask 表示所有的 cpu 核掩碼。
此后初始化 softlockup detector,在每個(gè) cpu 核上創(chuàng)建 watchdog_thread 線程,僅在 watchdog_cpumask 使能的每個(gè) cpu 核中喚醒 watchdog_thread 運(yùn)行,未使能的 cpu 核上創(chuàng)建的 watchdog_thread 不會(huì)運(yùn)行。watchdog_thread 線程模擬看門狗周期性更新 softlockup watchdog,實(shí)現(xiàn)類似于“喂狗”的操作。
系統(tǒng)中的 watchdog/x 線程:
[root@localhost]# ps aux |grep watchdog
root 12 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/0]
root 13 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/1]
root 20 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/2]
root 27 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/3]
root 34 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/4]
root 41 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/5]
root 48 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/6]
root 55 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/7]
root 62 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/8]
root 69 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/9]
root 76 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/10]
root 83 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/11]
root 90 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/12]
root 97 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/13]
root 104 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/14]
root 111 0.0 0.0 0 0 ? S 02:52 0:00 [watchdog/15]
watchdog_threads 定義如下:
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,.cleanup = watchdog_cleanup,.park = watchdog_disable,.unpark = watchdog_enable,
};
相關(guān)字段的含義如下:
回調(diào) | 觸發(fā)時(shí)機(jī) | 作用 |
---|---|---|
store | 在 thread_fn() 內(nèi)部訪問 | 存儲(chǔ) watchdog 的全局變量 |
thread_should_run() | 線程調(diào)度時(shí)檢查 | 決定 watchdog 線程是否需要運(yùn)行 |
thread_fn() | thread_should_run() 返回 true 時(shí)執(zhí)行 | 執(zhí)行 watchdog 邏輯,檢測(cè)軟鎖 |
setup() | 線程創(chuàng)建時(shí)(CPU 上線) | 啟用 watchdog,初始化數(shù)據(jù) |
cleanup() | CPU 下線,線程銷毀 | 釋放資源,關(guān)閉 watchdog |
park() | CPU 下線時(shí) | 線程進(jìn)入暫停狀態(tài),不再運(yùn)行 |
unpark() | CPU 重新上線 | 重新啟用 watchdog 線程 |
thread_comm | 線程創(chuàng)建時(shí) | 設(shè)定 watchdog 線程的名稱 |
這里主要描述 setup 函數(shù)與 thread_should_run 及 thread_fn 函數(shù)。setup 函數(shù)在線程執(zhí)行時(shí)做初始化的操作,這里對(duì)應(yīng)的 watchdog_enable 函數(shù),其代碼如下:
static void watchdog_set_prio(unsigned int policy, unsigned int prio)
{struct sched_param param = { .sched_priority = prio };sched_setscheduler(current, policy, ¶m);
}static void watchdog_enable(unsigned int cpu)
{struct hrtimer *hrtimer = raw_cpu_ptr(&watchdog_hrtimer);/* kick off the timer for the hardlockup detector */hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);hrtimer->function = watchdog_timer_fn;/* Enable the perf event */watchdog_nmi_enable(cpu);/* done here because hrtimer_start can only pin to smp_processor_id() */hrtimer_start(hrtimer, ns_to_ktime(sample_period),HRTIMER_MODE_REL_PINNED);/* initialize timestamp */watchdog_set_prio(SCHED_FIFO, MAX_RT_PRIO - 1);__touch_watchdog();
}
它會(huì)初始化用于檢測(cè) softlockup 的 hrtimer,此 hrtimer 周期性運(yùn)行檢測(cè)是否發(fā)生了 softlockup,當(dāng)檢測(cè)到有 softlockup 發(fā)生時(shí),根據(jù)配置觸發(fā) panic、打印信息。
同時(shí)這里還做了 hardlockup 初始化,老版本內(nèi)核 hardlockup 使用 perf 注冊(cè)一個(gè)周期性的 nmi 中斷,在中斷中執(zhí)行 hardlockup 檢測(cè)。注意它還將線程的調(diào)度策略設(shè)置為 SCHED_FIFO 并將優(yōu)先級(jí)設(shè)置為最大以優(yōu)先調(diào)度,最后立刻出發(fā)了一次喂狗操作,避免錯(cuò)誤檢測(cè)。
thread_should_run 函數(shù)的觸發(fā)時(shí)機(jī):
- 調(diào)度器決定是否喚醒 watchdog 線程 時(shí)調(diào)用。
- 每次 CPU 進(jìn)入/退出空閑狀態(tài)、上下文切換 時(shí)都會(huì)檢查該函數(shù)
當(dāng)返回 true 時(shí)調(diào)度器會(huì)執(zhí)行 watchdog 線程,返回 false 則不需要執(zhí)行。
經(jīng)過上述分析,確定開啟了 nohz_full 功能的核上不會(huì)使能 softlockup 檢測(cè),cat /proc/cmdline 發(fā)現(xiàn)我們并未設(shè)置 nohz_full 相關(guān)內(nèi)核引導(dǎo)參數(shù),繼續(xù)閱讀內(nèi)核源碼發(fā)現(xiàn)它由 NO_HZ_FULL_ALL 配置使能,繼續(xù)對(duì)此配置進(jìn)行分析。
NO_HZ_FULL_ALL 配置功能分析
內(nèi)核原文:
config NO_HZ_FULL_ALLbool "Full dynticks system on all CPUs by default (except CPU 0)"depends on NO_HZ_FULLhelpIf the user doesn't pass the nohz_full boot option todefine the range of full dynticks CPUs, consider that allCPUs in the system are full dynticks by default.Note the boot CPU will still be kept outside the range tohandle the timekeeping duty.
如果用戶沒有傳遞 nohz_full 啟動(dòng)參數(shù)來定義完整的 dynticks cpu 的范圍,則默認(rèn)所有 cpu 都開啟此模式。請(qǐng)注意,啟動(dòng) cpu(cpu 0)仍將保持在范圍之外以處理定時(shí)任務(wù)。
測(cè)試記錄如下:
[root@localhost]# dmesg | grep NO_HZ
[ 0.000000] NO_HZ: Clearing 0 from nohz_full range for timekeeping
[ 0.000000] NO_HZ: Full dynticks CPUs: 1-127.
除引導(dǎo)核外,所有使能了 nohz_full 功能的 cpu 核會(huì)從 housekeeping 掩碼中去掉,這樣在 softlockup 初始化的時(shí)候就不會(huì)在這些核上啟動(dòng)檢測(cè) softlockup 的 hrtimer 定時(shí)器事件,這就是內(nèi)核沒有檢測(cè)到軟鎖并觸發(fā) panic 的根本原因。
同時(shí) softlockup 檢測(cè)的作用范圍是單個(gè)核,一個(gè)核上的定時(shí)器事件只能檢測(cè)該核上的 softlockup,這樣即便 0 核上開啟了 softlokcup 檢測(cè),但是觸發(fā) softlockup 的核非 0 核時(shí),在問題場(chǎng)景也無法正常工作。
內(nèi)核主線移除 NO_HZ_FULL_ALL 配置的修改:
commit a7c8655b073d89303911c89d0fd9fc4be7631fbe
Author: Paul E. McKenney <paulmck@kernel.org>
Date: Thu Nov 30 15:36:35 2017 -0800sched/isolation: Eliminate NO_HZ_FULL_ALLCommit 6f1982fedd59 ("sched/isolation: Handle the nohz_full= parameter")broke CONFIG_NO_HZ_FULL_ALL=y kernels. This breakage is due to the codeunder CONFIG_NO_HZ_FULL_ALL failing to invoke the shiny new housekeepingfunctions. This means that rcutorture scenario TREE04 now emits RCU CPUstall warnings due to the RCU grace-period kthreads not being awakenedat a time of their choosing, or perhaps even not at all:[ 27.731422] rcu_bh kthread starved for 21001 jiffies! g18446744073709551369 c18446744073709551368 f0x0 RCU_GP_WAIT_FQS(3) ->state=0x402 ->cpu=3[ 27.731423] rcu_bh I14936 9 2 0x80080000[ 27.731435] Call Trace:[ 27.731440] __schedule+0x31a/0x6d0[ 27.731442] schedule+0x31/0x80[ 27.731446] schedule_timeout+0x15a/0x320[ 27.731453] ? call_timer_fn+0x130/0x130[ 27.731457] rcu_gp_kthread+0x66c/0xea0[ 27.731458] ? rcu_gp_kthread+0x66c/0xea0Because no one has complained about CONFIG_NO_HZ_FULL_ALL=y being broken,I hypothesize that no one is in fact using it, other than rcutorture.This commit therefore eliminates CONFIG_NO_HZ_FULL_ALL and updatesrcutorture's config files to instead use the nohz_full= kernel parameterto put the desired CPUs into nohz_full mode.Fixes: 6f1982fedd59 ("sched/isolation: Handle the nohz_full= parameter")Reported-by: kernel test robot <xiaolong.ye@intel.com>Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>Cc: Frederic Weisbecker <frederic@kernel.org>Cc: Thomas Gleixner <tglx@linutronix.de>Cc: Chris Metcalf <cmetcalf@mellanox.com>Cc: Christoph Lameter <cl@linux.com>Cc: Linus Torvalds <torvalds@linux-foundation.org>Cc: Luiz Capitulino <lcapitulino@redhat.com>Cc: Mike Galbraith <efault@gmx.de>Cc: Peter Zijlstra <peterz@infradead.org>Cc: Rik van Riel <riel@redhat.com>Cc: Wanpeng Li <kernellwp@gmail.com>Cc: Ingo Molnar <mingo@kernel.org>Cc: John Stultz <john.stultz@linaro.org>Cc: Jonathan Corbet <corbet@lwn.net>
linux 內(nèi)核 v4.15 版本移除了 NO_HZ_FULL_ALL 選項(xiàng),后續(xù)內(nèi)核版本不存在此問題。
問題延伸:watchdog_cpumask 配置
通過寫入 watchdog_cpumask
文件能夠動(dòng)態(tài)配置 watchdog 線程進(jìn)入暫停、運(yùn)行狀態(tài)以此來使能指定核上的 softlockup 檢測(cè)。
它保存了掩碼的值,不支持特殊的格式,當(dāng)寫入后,內(nèi)核會(huì)通過 park、unpark 機(jī)制來暫停、運(yùn)行指定核上的 watchdog_thread 線程來達(dá)到動(dòng)態(tài)配置的效果。
如何解決此問題?
根據(jù)分析情況,高版本內(nèi)核也已經(jīng)不具備此配置,關(guān)閉 CONFIG_NOHZ_FULL_ALL 配置能夠解決此問題,這里隱含著一個(gè)問題就是對(duì)于開啟了 nohz_full 的 cpu 核,軟硬鎖檢測(cè)功能將會(huì)失效。
總結(jié)
nohz_full 功能是針對(duì) cpu 性能的一個(gè)優(yōu)化,通過減少 cpu 核上運(yùn)行的時(shí)鐘中斷來提高程序性能。需要注意的是開啟了此功能對(duì)現(xiàn)有內(nèi)核檢測(cè)機(jī)制的影響,例如這里的 softlockup 檢測(cè)失效的問題。
softlockup 檢測(cè)作為一種可靠性的功能,此問題的存在表明產(chǎn)品在追求高性能的同時(shí)設(shè)計(jì)中也需要兼顧可靠性能力,有時(shí)候這兩者可能還存在一些沖突,需要權(quán)衡。