自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux 內(nèi)存變低會(huì)發(fā)生什么問(wèn)題

系統(tǒng) Linux
內(nèi)存不是無(wú)限的,總有不夠用的時(shí)候,Linux內(nèi)核用三個(gè)機(jī)制來(lái)處理這種情況:內(nèi)存回收、內(nèi)存規(guī)整、oom-kill。

作者 | cynrikluo

內(nèi)存不是無(wú)限的,總有不夠用的時(shí)候,linux內(nèi)核用三個(gè)機(jī)制來(lái)處理這種情況:內(nèi)存回收、內(nèi)存規(guī)整、oom-kill。

當(dāng)發(fā)現(xiàn)內(nèi)存不足時(shí),內(nèi)核會(huì)先嘗試內(nèi)存回收,從一些進(jìn)程手里拿回一些頁(yè);如果這樣還是不能滿(mǎn)足申請(qǐng)需求,則觸發(fā)內(nèi)存規(guī)整;再不行,則觸發(fā)oom主動(dòng)kill掉一個(gè)不太重要的進(jìn)程,釋放內(nèi)存。

低內(nèi)存情況下,內(nèi)核的處理邏輯

內(nèi)存申請(qǐng)的核心函數(shù)是__alloc_pages_nodemask:

/*
 * This is the 'heart' of the zoned buddy allocator.
 */
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,
              nodemask_t *nodemask)
{
  struct page *page;
  unsigned int alloc_flags = ALLOC_WMARK_LOW;
  gfp_t alloc_mask; /* The gfp_t that was actually used for allocation */
  struct alloc_context ac = { }; 

__alloc_pages_nodemask會(huì)先嘗試調(diào)用get_page_from_freelist從伙伴系統(tǒng)的freelist里拿空閑頁(yè),如果能拿到就直接返回:

如果拿不到,則進(jìn)入慢速路徑:

__alloc_pages_slowpath,慢速路徑,顧名思義,就是拿得慢一點(diǎn),需要做一些操作以后再拿。

首先, __alloc_pages_slowpath會(huì)喚醒kswapd:

kswapd是一個(gè)守護(hù)進(jìn)程,專(zhuān)門(mén)進(jìn)行內(nèi)存回收操作,執(zhí)行路徑:

它被喚醒后,會(huì)立1刻開(kāi)始進(jìn)行回收,效率高的話,freelist上會(huì)立刻多出很多空閑頁(yè)。

所以 __alloc_pages_slowpath會(huì)馬上再次嘗試從freelist獲取頁(yè)面,獲取成功則直接返回了。

若還是失敗, __alloc_pages_slowpath則會(huì)進(jìn)入direct_reclaim階段:

direct_reclaim,顧名思義,就是直接內(nèi)存回收,回收到的頁(yè)不用放回freelist再get_page_from_freelist這么麻煩了,也不用喚醒某個(gè)進(jìn)程幫忙回收,而是由當(dāng)前進(jìn)程(current)親自下場(chǎng)去回收,執(zhí)行路徑:

如果direct_reclaim也回收不上來(lái), __alloc_pages_slowpath還會(huì)垂死掙扎下,做一下內(nèi)存規(guī)整,嘗試把零散的頁(yè)輾轉(zhuǎn)騰挪,拼成為大order頁(yè)(僅在申請(qǐng)order>0的頁(yè)時(shí)有用)。

如果還是無(wú)法滿(mǎn)足要求,則進(jìn)入oom-kill了:

總結(jié)上面的邏輯:內(nèi)存申請(qǐng)時(shí),首先嘗試直接從freelist里拿;失敗了則先喚醒kswapd幫忙回收內(nèi)存;若內(nèi)存低到讓kswapd也愛(ài)莫能助,則進(jìn)入direct reclaim直接回收內(nèi)存;若direct reclaim也無(wú)能為力,則oom:

三條水線

實(shí)際上,從freelist上拿頁(yè)不是簡(jiǎn)單地直接拿,而是先檢查下該zone是否滿(mǎn)足水線要求,不滿(mǎn)足那就直接失敗。

內(nèi)核給內(nèi)存管理劃了三條水線:MIN、LOW、HIGH。

三者大小關(guān)系從字面即可推斷,MIN < LOW < HIGH。

在首次嘗試從freelist拿頁(yè)時(shí),門(mén)檻水線是LOW;喚醒kswapd后再次嘗試拿頁(yè),門(mén)檻水線是MIN。

所以實(shí)際邏輯如下:

所以,可以簡(jiǎn)單地認(rèn)為,可用內(nèi)存低于LOW水線時(shí),喚醒kswapd;低于MIN水線時(shí),進(jìn)行direct reclaim;而HIGH水線,是kswapd的回收終止線:

為什么內(nèi)存回收時(shí),磁盤(pán)IO會(huì)被打滿(mǎn)?

可以看到,kswapd和direct_reclaim最終都是走到了shrink_node:

shrink_node是內(nèi)存回收的核心函數(shù),顧名思義,讓整個(gè)node進(jìn)行一次“收縮”,把不要的數(shù)據(jù)清掉,空出空閑頁(yè)。

get_scan_count決定本次掃描多少個(gè)anon page和file page。

anon page就是Anonymous Page,匿名頁(yè),是進(jìn)程的堆棧、數(shù)據(jù)段等。內(nèi)核回收匿名頁(yè)時(shí),將這些數(shù)據(jù)進(jìn)行壓縮(壓縮比大概為3),然后移動(dòng)到內(nèi)存中的一個(gè)小角落中(swap空間),這個(gè)過(guò)程并沒(méi)有與磁盤(pán)發(fā)生交互,因此不會(huì)產(chǎn)生IO,但需要壓縮數(shù)據(jù),所以耗CPU。

file page就是文件頁(yè),是進(jìn)程的代碼段、映射的文件。內(nèi)核回收文件頁(yè)時(shí),先將“臟”數(shù)據(jù)回寫(xiě)到磁盤(pán),然后釋放掉這些緩存數(shù)據(jù),干凈的數(shù)據(jù)則直接釋放掉。這個(gè)過(guò)程涉及到寫(xiě)磁盤(pán),因此會(huì)產(chǎn)生IO。

簡(jiǎn)單總結(jié)一下get_scan_count的邏輯:

所以說(shuō),不論開(kāi)沒(méi)開(kāi)swap,內(nèi)存回收都是傾向于回收f(shuō)ile page。

如果file page中有臟頁(yè),那內(nèi)存回收大概率就會(huì)產(chǎn)生一些IO,無(wú)非是IO量多少罷了。

以下情況IO可能會(huì)打滿(mǎn)或者暴增:

  • 當(dāng)前內(nèi)存不是特別緊張,但low、min水線設(shè)置得太低,之前一直沒(méi)怎么觸發(fā)過(guò)內(nèi)存回收,以致于臟頁(yè)已經(jīng)累積到大量,一觸發(fā)回收,立刻就是回寫(xiě)大量臟頁(yè),導(dǎo)致IO暴增。
  • 內(nèi)存極度緊張 (free 和available同時(shí)很低)。這種情況下,anon page遠(yuǎn)比f(wàn)ile page多,這意味著可回收的內(nèi)存很少,內(nèi)核會(huì)對(duì)活躍數(shù)據(jù)下手,一些進(jìn)程上一秒還用著的數(shù)據(jù),這一秒可能就被不幸回收了,但下一秒馬上又要被使用,會(huì)再次被讀入內(nèi)存。如此,同一份數(shù)據(jù),內(nèi)核就進(jìn)行了多次回收和讀入,IO就加倍了。

為什么低內(nèi)存有時(shí)會(huì)引發(fā)hungtask?

低內(nèi)存時(shí),通常不是個(gè)別進(jìn)程觸發(fā)了direct reclaim,而是大量進(jìn)程都在direct reclaim。

大家都要回寫(xiě)臟頁(yè),于是IO被打滿(mǎn)了。

這時(shí)候,進(jìn)程會(huì)頻繁地被IO阻塞,被阻塞的進(jìn)程為了不占用CPU,會(huì)調(diào)用io_schedule_timeout或io_schedule來(lái)掛起自己,直到IO完成。

這種等待是D狀態(tài)的,一旦超過(guò)了120S,就會(huì)觸發(fā)hungtask。當(dāng)然,這是非常極端的情況,IO已經(jīng)完全沒(méi)救的情況。

大部分時(shí)候,IO雖然打滿(mǎn)了,但是總能周轉(zhuǎn)過(guò)來(lái),所以這些進(jìn)程并不會(huì)等太久。

然而,這些進(jìn)程若是來(lái)自同一個(gè)業(yè)務(wù),則大概率會(huì)訪問(wèn)同一個(gè)數(shù)據(jù),這就需要通過(guò)mutex、rwsem、semaphore等同步機(jī)制來(lái)控制訪問(wèn)行為。

而這些同步機(jī)制的基本接口都是uninterruptible性質(zhì)的,以semaphore為例:

extern void down(struct semaphore *sem); // 基本接口。獲取信號(hào)量,獲取不到則進(jìn)入uninterruptible睡眠
extern int __must_check down_interruptible(struct semaphore *sem); // 其他接口
extern int __must_check down_killable(struct semaphore *sem); // 其他接口
extern int __must_check down_trylock(struct semaphore *sem); // 其他接口
extern int __must_check down_timeout(struct semaphore *sem, long jiffies); // 其他接口

所謂uninterruptible性質(zhì),即當(dāng)進(jìn)程獲取不到同步資源時(shí),直接進(jìn)入D狀態(tài)等待其他進(jìn)程釋放資源。

其他同步資源,rwsem、mutex等,都有這樣的uninterruptible性質(zhì)接口。

正常情況下,只要持有同步資源的進(jìn)程正常運(yùn)行不卡頓,那么即使有上百個(gè)進(jìn)程來(lái)爭(zhēng)搶這些同步資源,對(duì)于排序靠后的進(jìn)程來(lái)說(shuō),時(shí)間也是夠的,一般不會(huì)等待超過(guò)120s。

但在低內(nèi)存情況下,大家都在等IO,這些持有資源的進(jìn)程也不能幸免,引發(fā)堵車(chē)連鎖反應(yīng)。

如果此時(shí)同步資源的waiter們已累計(jì)了幾十個(gè)甚至上百個(gè),那么就算只有一瞬間的io卡頓,排序靠后的waiter也容易等待超過(guò)120s,觸發(fā)hungtask。

一個(gè)非常典型的案例,一臺(tái)CVM在連續(xù)報(bào)了幾條hungtask warning后,徹底無(wú)響應(yīng)了,通過(guò)魔術(shù)建觸發(fā)重啟。

系統(tǒng)信息如下:

內(nèi)存狀況不容樂(lè)觀,典型的低內(nèi)存:

log上有很多hungtask warning,超時(shí)原因都是等rwsem太長(zhǎng),寫(xiě)者waiter和讀者waiter都有:

這些進(jìn)程在等同一個(gè)rwsem,這個(gè)rwsem的地址為:ffff880e9703f370

進(jìn)一步探究,發(fā)現(xiàn)當(dāng)前對(duì)ffff880e9703f370有引用的進(jìn)程為19個(gè),11個(gè)正在讀,8個(gè)排隊(duì)。

而這11個(gè)正在讀的進(jìn)程,都在做同一件事——direct reclaim,并且都卡在IO等待:

這11個(gè)進(jìn)程,雖然也是D狀態(tài),但由于時(shí)不時(shí)能調(diào)度到IO,相當(dāng)于D狀態(tài)的持續(xù)時(shí)間不斷重置,所以本身并沒(méi)有觸發(fā)hungtask。

而這8個(gè)waiter進(jìn)程就沒(méi)這個(gè)好運(yùn)了,被前面11個(gè)進(jìn)程你方唱罷我登場(chǎng)地阻塞,持續(xù)時(shí)間也沒(méi)有機(jī)會(huì)重置,最終超過(guò)120s,引發(fā)hungtask了。

優(yōu)化低內(nèi)存處理

我們已經(jīng)知道了低內(nèi)存會(huì)導(dǎo)致IO突增,甚至導(dǎo)致hungtask,那要如何避免呢?

可以從兩方面來(lái)避免。

(1) 調(diào)整臟頁(yè)回刷頻率

將平時(shí)的臟頁(yè)回刷頻率調(diào)高,這樣內(nèi)存回收時(shí),需要回收的臟頁(yè)就更少,降低IO的增量。

  • 調(diào)低 /proc/sys/vm/dirty_writeback_centisecs
  • 調(diào)低/proc/sys/vm/dirty_background_ratio
(2) 調(diào)高low水線和min水線

調(diào)高水線,可以更早地進(jìn)入內(nèi)存回收邏輯,這樣可以將free維持在一個(gè)較高水平,避免陷入極端場(chǎng)景。由于low和min同時(shí)受min_free_kbytes管控,所以可以直接調(diào)整min_free_kbytes值。

調(diào)高/proc/sys/vm/min_free_kbytes

責(zé)任編輯:趙寧寧 來(lái)源: 騰訊技術(shù)工程
相關(guān)推薦

2023-08-26 07:44:13

系統(tǒng)內(nèi)存虛擬

2021-03-10 10:40:04

Redis命令Linux

2020-12-10 07:37:42

HashMap數(shù)據(jù)覆蓋

2021-08-19 17:27:41

IT數(shù)據(jù)中心災(zāi)難

2021-12-27 08:24:08

漏洞網(wǎng)絡(luò)安全

2015-09-25 10:41:48

r語(yǔ)言

2023-06-27 16:53:50

2015-11-19 00:11:12

2016-01-04 11:03:00

2015-04-16 10:40:29

2023-04-27 07:40:08

Spring框架OpenAI

2024-01-18 11:50:28

2019-02-27 10:18:26

重置Windows 10Windows

2011-10-11 15:42:54

大數(shù)據(jù)數(shù)據(jù)庫(kù)

2012-12-25 15:19:20

Windows操作系統(tǒng)

2020-12-16 19:26:42

IIOTIOT工業(yè)物聯(lián)網(wǎng)

2019-03-14 11:00:40

GoLua語(yǔ)言

2020-06-15 08:06:25

ES數(shù)據(jù)

2018-06-06 00:26:20

SDN5G無(wú)線網(wǎng)絡(luò)

2023-05-04 00:16:39

數(shù)字化轉(zhuǎn)型運(yùn)營(yíng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)