1. 程式人生 > >Linux 記憶體資源管理

Linux 記憶體資源管理

Hi ,我是 Zorro 。這是我的微博地址,我會不定期在這裡更新文章,如果你有興趣,可以來關注我呦。

另外,我的其他聯絡方式:

QQ: 30007147

本文PDF

在聊 cgroup 的記憶體限制之前,我們有必要先來講解一下:

Linux 記憶體管理基礎知識

free 命令

無論從任何角度看, Linux 的記憶體管理都是一坨麻煩的事情,當然我們也可以用一堆、一片、一塊、一筐來形容這個事情,但是毫無疑問,用一坨來形容它簡直恰當無比。在理解它之前,我甚至不會相信精妙的和噁心可以同時形容同一件事情,是的,在我看來它就是這樣的。其實我只是做個鋪墊,讓大家明白,我們下面要講的內容,絕不是一個成體系的知識,所以,學習起來也確實很麻煩。甚至,我寫這個技術文章之前一度考慮了很久該怎麼寫?從哪裡開始寫?思考了半天,還是不能免俗,我們無奈,仍然先從 free 命令說起:

[[email protected] ~]# free
             total       used       free     shared    buffers     cached
Mem:     131904480    6681612  125222868          0     478428    4965180
-/+ buffers/cache:    1238004  130666476
Swap:      2088956          0    2088956

這個命令幾乎是每一個使用過 Linux 的人必會的命令,但越是這樣的命令,似乎真正明白的人越少(我是說比例越少)。一般情況下,對此命令的理解可以分這幾個階段:

  1. 我擦,記憶體用了好多, 6 個多 G ,可是我什麼都沒有執行啊?為什麼會這樣? Linux 好佔記憶體。
  2. 嗯,根據我專業的眼光看出來,記憶體才用了 1G 多點,還有很多剩餘記憶體可用。 buffers/cache 佔用的較多,說明系統中有程序曾經讀寫過檔案,但是不要緊,這部分記憶體是當空閒來用的。
  3. free 顯示的是這樣,好吧我知道了。神馬?你問我這些記憶體夠不夠,我當然不知道啦!我特麼怎麼知道你程式怎麼寫的?

如果你的認識在第一種階段,那麼請你繼續補充關於 Linux 的 buffers / cache 的知識。如果你處在第二階段,好吧,你已經是個老手了,但是需要提醒的是,上帝給你關上一扇門的同時,肯定都會給你放一條狗的。是的, Linux 的策略是:記憶體是用來用的,而不是用來看的。但是,只要是用了,就不是沒有成本的。有什麼成本,憑你對 buffer/cache 的理解,應該可以想的出來。一般我比較認同第三種情況,一般光憑一個 free 命令的顯示,是無法判斷出任何有價值的資訊的,我們需要結合業務的場景以及其他輸出綜合判斷目前遇到的問題。當然也可能這種人給人的第一感覺是他很外行,或者他真的是外行。

無論如何, free 命令確實給我門透露了一些有用的資訊,比如記憶體總量,剩餘多少,多少用在了 buffers / cache 上, Swap 用了多少,如果你用了其它引數還能看到一些其它內容,這裡不做一一列舉。那麼這裡又引申出另一些概念,什麼是 buffer ?什麼是 cache ?什麼是 swap ?由此我們就直接引出另一個命令:

[[email protected] ~]# cat /proc/meminfo
MemTotal:       131904480 kB
MemFree:        125226660 kB
Buffers:          478504 kB
Cached:          4966796 kB
SwapCached:            0 kB
Active:          1774428 kB
Inactive:        3770380 kB
Active(anon):     116500 kB
Inactive(anon):     3404 kB
Active(file):    1657928 kB
Inactive(file):  3766976 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:       2088956 kB
SwapFree:        2088956 kB
Dirty:               336 kB
Writeback:             0 kB
AnonPages:         99504 kB
Mapped:            20760 kB
Shmem:             20604 kB
Slab:             301292 kB
SReclaimable:     229852 kB
SUnreclaim:        71440 kB
KernelStack:        3272 kB
PageTables:         3320 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:    68041196 kB
Committed_AS:     352412 kB
VmallocTotal:   34359738367 kB
VmallocUsed:      493196 kB
VmallocChunk:   34291062284 kB
HardwareCorrupted:     0 kB
AnonHugePages:     49152 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:      194816 kB
DirectMap2M:     3872768 kB
DirectMap1G:    132120576 kB

以上顯示的內容都是些什麼鬼?

其實這個問題的答案也是另一個問題的答案,即: Linux 是如何使用記憶體的?瞭解清楚這個問題是很有必要的,因為只有先知道了 Linux 如何使用記憶體,我們在能知道記憶體可以如何限制,以及,做了限制之後會有什麼問題?我們在此先例舉出幾個常用概念的意義:

記憶體,作為一種相對比較有限的資源,核心在考慮其管理時,無非應該主要從以下出發點考慮:

  1. 記憶體夠用時怎麼辦?
  2. 記憶體不夠用時怎麼辦?

在記憶體夠用時,核心的思路是,如何儘量提高資源的利用效率,以加快系統整體響應速度和吞吐量?於是記憶體作為一個 CPU 和 I / O 之間的大 buffer 的功能就呼之欲出了。為此,核心設計了以下系統來做這個功能:

Buffers / Cached

buffer 和 cache 是兩個在計算機技術中被用濫的名詞,放在不通語境下會有不同的意義。在記憶體管理中,我們需要特別澄清一下,這裡的 buffer 指 Linux 記憶體的: Buffer cache 。這裡的 cache 指 Linux 記憶體中的: Page cache 。翻譯成中文可以叫做緩衝區快取和頁面快取。在歷史上,它們一個( buffer )被用來當成對 io 裝置寫的快取,而另一個( cache )被用來當作對 io 裝置的讀快取,這裡的 io 裝置,主要指的是塊裝置檔案和檔案系統上的普通檔案。但是現在,它們的意義已經不一樣了。在當前的核心中, page cache 顧名思義就是針對記憶體頁的快取,說白了就是,如果有記憶體是以 page 進行分配管理的,都可以使用 page cache 作為其快取來使用。當然,不是所有的記憶體都是以頁( page )進行管理的,也有很多是針對塊( block )進行管理的,這部分記憶體使用如果要用到 cache 功能,則都集中到 buffer cache 中來使用。(從這個角度出發,是不是 buffer cache 改名叫做 block cache 更好?)然而,也不是所有塊( block )都有固定長度,系統上塊的長度主要是根據所使用的塊裝置決定的,而頁長度在 X86 上無論是 32 位還是 64 位都是 4k 。

而明白了這兩套快取系統的區別,也就基本可以理解它們究竟都可以用來做什麼了。

什麼是 page cache

Page cache 主要用來作為檔案系統上的檔案資料的快取來用,尤其是針對當程序對檔案有 read / write 操作的時候。如果你仔細想想的話,作為可以對映檔案到記憶體的系統呼叫: mmap 是不是很自然的也應該用到 page cache ?如果你再仔細想想的話, malloc 會不會用到 page cache ?

以上提出的問題都請自己思考,本文件不會給出標準答案。

在當前的實現裡, page cache 也被作為其它檔案型別的快取裝置來用,所以事實上 page cache 也負責了大部分的塊裝置檔案的快取工作。

什麼是 buffer cache

Buffer cache 則主要是設計用來在系統對塊裝置進行讀寫的時候,對塊進行資料快取的系統來使用。但是由於 page cache 也負責塊裝置檔案讀寫的快取工作,於是,當前的 buffer cache 實際上要負責的工作比較少。這意味著某些對塊的操作會使用 buffer cache 進行快取,比如我們在格式化檔案系統的時候。

一般情況下兩個快取系統是一起配合使用的,比如當我們對一個檔案進行寫操作的時候, page cache 的內容會被改變,而 buffer cache 則可以用來將 page 標記為不同的緩衝區,並記錄是哪一個緩衝區被修改了。這樣,核心在後續執行髒資料的回寫( writeback )時,就不用將整個 page 寫回,而只需要寫回修改的部分即可。

有搞大型系統經驗的人都知道,快取就像萬金油,只要哪裡有速度差異產生的瓶頸,就可以在哪裡抹。但是其成本之一就是,需要維護資料的一致性。記憶體快取也不例外,核心需要維持其一致性,在髒資料產生較快或資料量較大的時候,快取系統整體的效率一樣會下降,因為畢竟髒資料寫回也是要消耗 IO 的。這個現象也會表現在這樣一種情況下,就是當你發現 free 的時候,記憶體使用量較大,但是去掉了 buffer / cache 的使用之後剩餘確很多。以一般的理解,都會認為此時程序如果申請記憶體,核心會將 buffer / cache 佔用的記憶體當成空閒的記憶體分給程序,這是沒錯的。但是其成本是,在分配這部分已經被 buffer / cache 佔用的記憶體的時候,核心會先對其上面的髒資料進行寫回操作,保證資料一致後才會清空並分給程序使用。如果此時你的程序是突然申請大量記憶體,而且你的業務是一直在產生很多髒資料(比如日誌),並且系統沒有及時寫回的時候,此時系統給程序分配記憶體的效率會很慢,系統 IO 也會很高。那麼此時你還以為 buffer / cache 可以當空閒記憶體使用麼?

思考題: Linux 什麼時候會將髒資料寫回到外部裝置上?這個過程如何進行人為干預?

這足可以證明一點,以記憶體管理的複雜度,我們必須結合系統上的應用狀態來評估系統監控命令所給出的資料,才是做評估的正確途徑。如果你不這樣做,那麼你就可以輕而易舉的得出“ Linux 系統好爛啊!“這樣的結論。也許此時,其實是你在這個系統上跑的應用很爛的緣故導致的問題。

接下來,當記憶體不夠用的時候怎麼辦?

我們好像已經分析了一種記憶體不夠用的狀態,就是上述的大量 buffer / cache 把記憶體幾乎佔滿的情況。但是基於 Linux 對記憶體的使用原則,這不算是不夠用,但是這種狀態導致 IO 變高了。我們進一步思考,假設系統已經清理了足夠多的 buffer / cache 分給了記憶體,而程序還在嚷嚷著要記憶體咋辦?

此時核心就要啟動一系列手段來讓程序儘量在此時能夠正常的執行下去。

請注意我在這說的是一種異常狀態!我之所以要這樣強調是因為,很多人把記憶體用滿了當稱一種正常狀態。他們認為,當我的業務程序在記憶體使用到壓力邊界的情況下,系統仍然需要保證讓業務程序有正常的狀態!這種想法顯然是緣木求魚了。另外我還要強調一點,系統提供的是記憶體管理的機制和手段,而記憶體用的好不好,主要是業務程序的事情,責任不能本末倒置。

誰該 SWAP ?

首先是 Swap 機制。 Swap 是交換技術,這種技術是指,當記憶體不夠用的時候,我們可以選擇性的將一塊磁碟、分割槽或者一個檔案當成交換空間,將記憶體上一些臨時用不到的資料放到交換空間上,以釋放記憶體資源給急用的程序。

哪些資料可能會被交換出去呢?從概念上判斷,如果一段記憶體中的資料被經常訪問,那麼就不應該被交換到外部裝置上,因為這樣的資料如果交換出去的話會導致系統響應速度嚴重下降。記憶體管理需要將記憶體區分為活躍的( Active )和不活躍的( Inactive ),再加上一個程序使用的使用者空間記憶體對映包括檔案影射( file )和匿名影射( anon ),所以就包括了 Active ( anon )、 Inactive ( anon )、 Active ( file )和 Inactive ( file )。你說神馬?啥是檔案影射( file )和匿名影射( anon )?好吧,我們可以這樣簡單的理解,匿名影射主要是諸如程序使用 malloc 和 mmap 的 MAP_ANONYMOUS 的方式申請的記憶體,而檔案影射就是使用 mmap 影射的檔案系統上的檔案,這種檔案系統上的檔案既包括普通的檔案,也包括臨時檔案系統( tmpfs )。這意味著, Sys V 的 IPC 和 POSIX 的 IPC ( IPC 是程序間通訊機制,在這裡主要指共享記憶體,訊號量陣列和訊息佇列)都是通過檔案影射方式體現在使用者空間記憶體中的。這兩種影射的記憶體都會被算成程序的 RSS ,但是也一樣會被顯示在 cache 的記憶體計數中,在相關 cgroup 的另一項統計中,共享記憶體的使用和檔案快取( file cache )也都會被算成是 cgroup 中的 cache 使用的總量。這個統計顯示的方法是:

[[email protected] ~]# cat /cgroup/memory/memory.stat
cache 94429184
rss 102973440
rss_huge 50331648
mapped_file 21512192
swap 0
pgpgin 656572990
pgpgout 663474908
pgfault 2871515381
pgmajfault 1187
inactive_anon 3497984
active_anon 120524800
inactive_file 39059456
active_file 34484224
unevictable 0
hierarchical_memory_limit 9223372036854775807
hierarchical_memsw_limit 9223372036854775807
total_cache 94429184
total_rss 102969344
total_rss_huge 50331648
total_mapped_file 21520384
total_swap 0
total_pgpgin 656572990
total_pgpgout 663474908
total_pgfault 2871515388
total_pgmajfault 1187
total_inactive_anon 3497984
total_active_anon 120524800
total_inactive_file 39059456
total_active_file 34484224
total_unevictable 0

好吧,說了這麼半天終於聯絡到一個 cgroup 的記憶體限制相關的檔案了。在這需要說明的是,你之所以看見我廢話這麼多,是因為我們必須先基本理清楚 Linux 系統的記憶體管理方式,才能進一步對 cgroup 中的記憶體限制做規劃使用,否則同樣的名詞會有很多的歧義。就比如我們在觀察某一個 cgroup 中的 cache 佔用資料的時候,我們究竟該怎麼理解它?真的把它當成空閒空間來看麼?

我們撤的有點遠,回過頭來說說這些跟 Swap 有什麼關係?還是剛才的問題,什麼內容該被從記憶體中交換出去呢?檔案 cache 是一定不需要的,因為既然是 cache ,就意味著它本身就是硬碟上的檔案(當然你現在應該知道了,它也不僅僅只有檔案),那麼如果是硬碟上的檔案,就不用 swap 交換出去,只要寫回髒資料,保持資料一致之後清除就可以了,這就是剛才說過的快取清楚機制。但是我們同時也要知道,並不是所有被標記為 cache 的空間都能被寫回硬碟的(是的,比如共享記憶體)。那麼能交換出去記憶體應該主要包括有 Inactive ( anon )這部分記憶體。主要注意的是,核心也將共享記憶體作為計數統計近了 Inactive ( anon )中去了(是的,共享記憶體也可以被 Swap )。還要補充一點,如果記憶體被 mlock 標記加鎖了,則也不會交換,這是對記憶體加 mlock 鎖的唯一作用。剛才我們討論的這些計數,很可能會隨著 Linux 核心的版本改變而產生變化,但是在比較長的一段時間內,我們可以這樣理解。

我們基本搞清了 swap 這個機制的作用效果,那麼既然 swap 是內部裝置和外部裝置的資料拷貝,那麼加一個快取就顯得很有必要,這個快取就是 swapcache ,在 memory.stat 檔案中, swapcache 是跟 anon page 被一起記錄到 rss 中的,但是並不包含共享記憶體。另外再說明一下, HugePages 也是不會交換的。顯然,當前的 swap 空間用了多少,總共多少,這些我們也可以在相關的資料中找到答案。

以上概念中還有一些名詞大家可能並不清楚其含義,比如 RSS 或 HugePages 。請自行查資料補上這些知識。為了讓大家真的理解什麼是 RSS ,請思考 ps aux 命令中顯示的 VSZ , RSS 和 cat /proc/pid/smaps 中顯示的: PSS 這三個程序佔用記憶體指標的差別?

何時 SWAP ?

搞清楚了誰該 swap ,那麼還要知道什麼時候該 swap 。這看起來比較簡單,記憶體耗盡而且 cache 也沒什麼可以回收的時候就應該觸發 swap 。其實現實情況也沒這麼簡單,實際上系統在記憶體壓力可能不大的情況下也會 swap ,這種情況並不是我們今天要討論的範圍。

思考題:除了記憶體被耗盡的時候要 swap ,還有什麼時候會 swap ?如何調整核心 swap 的行為?如何檢視當前系統的 swap 空間有哪些?都是什麼型別?什麼是 swap 權重? swap 權重有什麼意義?

其實絕大多數場景下,什麼時候 swap 並不重要,而 swap 之後的事情相對卻更重要。大多數的記憶體不夠用,只是臨時不夠用,比如併發突增等突發情況,這種情況的特點是時間持續短,此時 swap 機制作為一種臨時的中轉措施,可以起到對業務程序的保護作用。因為如果沒有 swap ,記憶體耗盡的結果一般都是觸發 oom killer ,會殺掉此時積分比較高的程序。如果更嚴重的話,記憶體不夠用還會觸發程序 D 狀態死鎖,這一般發生在多個程序同時要申請記憶體的時候,此時 oom killer 機制也可能會失效,因為需要被幹掉的積分比較高的程序很可能就是需要申請記憶體的程序,而這個程序本身因為正在爭搶記憶體而導致陷入 D 狀態,那麼此時 kill 就可能是對它無效的。

但是 swap 也不是任何時候都有很好的保護效果。如果記憶體申請是長期並大量的,那麼交換出去的資料就會因為長時間駐留在外部裝置上,導致程序呼叫這段記憶體的機率大大增加,當程序很頻繁的使用它已經被交換出去的記憶體時,就會讓整個系統處在 io 繁忙的狀態,此時程序的響應速度會嚴重下降,導致整個系統夯死。對於系統管理員來說,這種情況是完全不能接受的,因為故障之後的第一要務是趕緊恢復服務,但是 swap 頻繁使用的 IO 繁忙狀態會導致系統除了斷電重啟之外,沒有其它可靠手段可以讓系統從這種狀態中恢復回來,所以這種情況是要盡力避免的。此時,如果有必要,我們甚至可以考慮不用 swap ,哪怕記憶體過量使用被 oom ,或者程序 D 狀態都是比 swap 導致系統卡死的情況更好處理的狀態。如果你的環境需求是這樣的,那麼可以考慮關閉 swap 。

程序申請記憶體的時候究竟會發生什麼?

剛才我們從系統巨集觀的角度簡要說明了一下什麼是 buffer / cache 以及 swap 。下面我們從一個更加微觀的角度來把一個記憶體申請的過程以及相關機制什麼時候觸發給串聯起來。本文描述的過程是基於 Linux 3.10 核心版本的, Linux 4.1 基本過程變化不大。如果你想確認在你的系統上究竟是什麼樣子,請自行翻閱相關核心程式碼。

程序申請記憶體可能用到很多種方法,最常見的就是 malloc 和 mmap 。但是這對於我們並不重要,因為無論是 malloc 還是 mmap ,或是其他的申請記憶體的方法,都不會真正的讓核心去給程序分配一個實際的實體記憶體空間。真正會觸發分配實體記憶體的行為是缺頁異常

缺頁異常就是我們可以在 memory.stat 中看到的 total_pgfault ,這種異常一般分兩種,一種叫 major fault ,另一種叫 minor fault 。這兩種異常的主要區別是,程序所請求的記憶體資料是否會引發磁碟 io ?如果會引發,就是一個 majfault ,如果不引發,那就是 minfault 。就是說如果產生了 major fault ,這個資料基本上就意味著已經被交換到了 swap 空間上。

缺頁異常的處理過程大概可以整理為以下幾個路徑:

首先檢查要訪問的虛擬地址是否合法,如果合法則繼續查詢和分配一個物理頁,步驟如下:

  1. 檢查發生異常的虛擬地址是不是在物理頁表中不存在?如果是,並且是匿名影射,則申請置 0 的匿名影射記憶體,此時也有可能是影射了某種虛擬檔案系統,比如共享記憶體,那麼就去影射相關的記憶體區,或者發生 COW 寫時複製申請新記憶體。如果是檔案影射,則有兩種可能,一種是這個影射區是一個 page cache ,直接將相關 page cache 區影射過來即可,或者 COW 新記憶體存放需要影射的檔案內容。如果 page cache 中不存在,則說明這個區域已經被交換到 swap 空間上,應該去處理 swap 。
  2. 如果頁表中已經存在需要影射的記憶體,則檢查是否要對記憶體進行寫操作,如果不寫,那就直接複用,如果要寫,就發生 COW 寫時複製,此時的 COW 跟上面的處理過程不完全相同,在核心中,這裡主要是通過 do_wp_page 方法實現的。

如果需要申請新記憶體,則都會通過 allocpage_vma 申請新記憶體,而這個函式的核心方法是_alloc_pages_nodemask ,也就是 Linux 核心著名的記憶體管理系統夥伴系統的實現。

分配過程先會檢查空閒頁表中有沒有頁可以申請,實現方法是: getpage_from_freelist ,我們並不關心正常情況,分到了當然一切 ok 。更重要的是異常處理,如果空閒中沒有,則會進入_alloc_pages_slowpath 方法進行處理。這個處理過程的主邏輯大概這樣:

  1. 喚醒 kswapd 程序,把能換出的記憶體換出,讓系統有記憶體可用。
  2. 繼續檢檢視看空閒中是否有記憶體。有了就 ok ,沒有繼續下一步:
  3. 嘗試清理 page cache ,清理的時候會將程序置為 D 狀態。如果還申請不到記憶體則:
  4. 啟動 oom killer 幹掉一些程序釋放記憶體,如果這樣還不行則:
  5. 回到步驟 1 再來一次!

當然以上邏輯要符合一些條件,但是這一般都是系統預設的狀態,比如,你必須啟用 oom killer 機制等。另外這個邏輯中有很多其它狀態與本文無關,比如檢查記憶體水印、檢查是否是高優先順序記憶體申請等等,當然還有關於 numa 節點狀態的判斷處理,我沒有一一列出。另外,以上邏輯中,不僅僅只有清理 cache 的時候會使程序進入 D 狀態,還有其它邏輯也會這樣做。這就是為什麼在記憶體不夠用的情況下, oom killer 有時也不生效,因為可能要幹掉的程序正好陷入這個邏輯中的 D 狀態了。

以上就是記憶體申請中,大概會發生什麼的過程。當然,我們這次主要是真對本文的重點 cgroup 記憶體限制進行說明,當我們處理限制的時候,更多需要關心的是當記憶體超限了會發生什麼?對邊界條件的處理才是我們這次的主題,所以我並沒有對正常申請到的情況做細節說明,也沒有對使用者態使用 malloc 什麼時候使用 sbrk 還是 mmap 來申請記憶體做出細節說明,畢竟那是程式正常狀態的時候的事情,後續可以另寫一個記憶體優化的文章主要講解那部分。

下面我們該進入正題了:

Cgroup 記憶體限制的配置

當限制記憶體時,我們最好先想清楚如果記憶體超限了會發生什麼?該怎麼處理?業務是否可以接受這樣的狀態?這就是為什麼我們在講如何限制之前說了這麼多基礎知識的“廢話”。其實最簡單的莫過於如何進行限制了,我們的系統環境還是沿用上一次講解 CPU 記憶體隔離的環境,使用 cgconfig 和 cgred 服務進行 cgroup 的配置管理。還是建立一個 zorro 使用者,對這個使用者產生的程序進行記憶體限制。基礎配置方法不再多說,如果不知道的請參考這個文件

環境配置好之後,我們就可以來檢查相關檔案了。記憶體限制的相關目錄根據 cgconfig.config 的配置放在了 /cgroup/memory 目錄中,如果你跟我做了一樣的配置,那麼這個目錄下的內容應該是這樣的:

[[email protected] ~]# ls /cgroup/memory/
cgroup.clone_children  memory.failcnt                  memory.kmem.slabinfo                memory.kmem.usage_in_bytes  memory.memsw.limit_in_bytes      memory.oom_control          memory.usage_in_bytes  shrek
cgroup.event_control   memory.force_empty              memory.kmem.tcp.failcnt             memory.limit_in_bytes       memory.memsw.max_usage_in_bytes  memory.pressure_level       memory.use_hierarchy   tasks
cgroup.procs           memory.kmem.failcnt             memory.kmem.tcp.limit_in_bytes      memory.max_usage_in_bytes   memory.memsw.usage_in_bytes      memory.soft_limit_in_bytes           zorro
cgroup.sane_behavior   memory.kmem.limit_in_bytes      memory.kmem.tcp.max_usage_in_bytes  memory.meminfo              memory.move_charge_at_immigrate  memory.stat                 notify_on_release
jerry                  memory.kmem.max_usage_in_bytes  memory.kmem.tcp.usage_in_bytes      memory.memsw.failcnt        memory.numa_stat                 memory.swappiness           release_agent

其中, zorro 、 jerry 、 shrek 都是目錄概念跟 cpu 隔離的目錄樹結構類似。相關配置檔案內容:

[[email protected] ~]# cat /etc/cgconfig.conf    mount {
    cpu = /cgroup/cpu;
    cpuset  = /cgroup/cpuset;
    cpuacct = /cgroup/cpuacct;
    memory  = /cgroup/memory;
    devices = /cgroup/devices;
    freezer = /cgroup/freezer;
    net_cls = /cgroup/net_cls;
    blkio   = /cgroup/blkio;
}

group zorro {
    cpu {
        cpu.shares = 6000;
#       cpu.cfs_quota_us = "600000";
    }
    cpuset {
#       cpuset.cpus = "0-7,12-19";
#       cpuset.mems = "0-1";
    }
    memory {
    }
}

配置中添加了一個真對 memory 的空配置項,我們稍等下再給裡面新增配置。

[[email protected] ~]# cat /etc/cgrules.conf 
zorro       cpu,cpuset,cpuacct,memory   zorro
jerry       cpu,cpuset,cpuacct,memory   jerry
shrek       cpu,cpuset,cpuacct,memory   shrek

檔案修改完之後記得重啟相關服務:

[[email protected] ~]# service cgconfig restart
[[email protected] ~]# service cgred restart

讓我們繼續來看看真對記憶體都有哪些配置引數:

[[email protected] ~]# ls /cgroup/memory/zorro/
cgroup.clone_children  memory.kmem.failcnt             memory.kmem.tcp.limit_in_bytes      memory.max_usage_in_bytes        memory.memsw.usage_in_bytes      memory.soft_limit_in_bytes  
cgroup.event_control   memory.kmem.limit_in_bytes      memory.kmem.tcp.max_usage_in_bytes  memory.meminfo                   memory.move_charge_at_immigrate  memory.stat                 notify_on_release
cgroup.procs           memory.kmem.max_usage_in_bytes  memory.kmem.tcp.usage_in_bytes      memory.memsw.failcnt             memory.numa_stat                 memory.swappiness           tasks
memory.failcnt         memory.kmem.slabinfo            memory.kmem.usage_in_bytes          memory.memsw.limit_in_bytes      memory.oom_control               memory.usage_in_bytes
memory.force_empty     memory.kmem.tcp.failcnt         memory.limit_in_bytes               memory.memsw.max_usage_in_bytes  memory.pressure_level            memory.use_hierarchy

首先我們已經認識了 memory.stat 檔案了,這個檔案內容不能修改,它實際上是輸出當前 cgroup 相關記憶體使用資訊的。常見的資料及其含義我們剛才也已經說過了,在此不再複述。

cgroup 記憶體限制

memory.memsw.limit_in_bytes:記憶體+ swap 空間使用的總量限制。

memory.limit_in_bytes:記憶體使用量限制。

這兩項的意義很清楚了,如果你決定在你的 cgroup 中關閉 swap 功能,可以把兩個檔案的內容設定為同樣的值即可。至於為什麼相信大家都能想清楚。

OOM 控制

memory.oom_control:記憶體超限之後的 oom 行為控制。
這個檔案中有兩個值:

oom_kill_disable 0

預設為 0 表示開啟 oom killer ,就是說當記憶體超限時會觸發幹掉程序。如果設定為 1 表示關閉 oom killer ,此時記憶體超限不會觸發核心殺掉程序。而是將程序夯住( hang / sleep ),實際上核心中就是將程序設定為 D 狀態,並且將相關程序放到一個叫做 OOM-waitqueue 的佇列中。這時的程序可以 kill 殺掉。如果你想繼續讓這些程序執行,可以選擇這樣幾個方法:

  1. 增加記憶體,讓程序有記憶體可以繼續申請。
  2. 殺掉一些程序,讓本組內有記憶體可用。
  3. 把一些程序移到別的 cgroup 中,讓本 cgroup 內有記憶體可用。
  4. 刪除一些 tmpfs 的檔案,就是佔用記憶體的檔案,比如共享記憶體或者其它會佔用記憶體的檔案。

說白了就是,此時只有當 cgroup 中有更多記憶體可以用了,在 OOM-waitqueue 佇列中被掛起的程序就可以繼續運行了。

under_oom 0

這個值只是用來看的,它表示當前的 cgroup 的狀態是不是已經 oom 了,如果是,這個值將顯示為 1 。我們就是通過設定和監測這個檔案中的這兩個值來管理 cgroup 記憶體超限之後的行為的。在預設場景下,如果你使用了 swap ,那麼你的 cgroup 限制記憶體之後最常見的異常效果是 IO 變高,如果業務不能接受,我們一般的做法是關閉 swap ,那麼 cgroup 記憶體 oom 之後都會觸發 kill 掉程序,如果我們用的是 LXC 或者 Docker 這樣的容器,那麼還可能幹掉整個容器。當然也經常會因為 kill 程序的時候因為程序處在 D 狀態,而導致整個 Docker 或者 LXC 容器根本無法被殺掉。至於原因,在前面已經說的很清楚了。當我們遇到這樣的困境時該怎麼辦?一個好的辦法是,關閉 oom killer ,讓記憶體超限之後,程序掛起,畢竟這樣的方式相對可控。此時我們可以檢查 under_oom 的值,去看容器是否處在超限狀態,然後根據業務的特點決定如何處理業務。我推薦的方法是關閉部分程序或者重啟掉整個容器,因為可以想像,容器技術所承載的服務應該是在整體軟體架構上有容錯的業務,典型的場景是 web 服務。容器技術的特點就是生存週期短,在這樣的場景下,殺掉幾個程序或者幾個容器,都應該對整體服務的穩定性影響不大,而且容器的啟動速度是很快的,實際上我們應該認為,容器的啟動速度應該是跟程序啟動速度可以相媲美的。你的業務會因為死掉幾個程序而表現不穩定麼?如果不會,請放心的幹掉它們吧,大不了很快再啟動起來就是了。但是如果你的業務不是這樣,那麼請根據自己的情況來制定後續處理的策略。

當我們進行了記憶體限制之後,記憶體超限的發生頻率要比使用實體機更多了,因為限制的記憶體量一般都是小於實際實體記憶體的。所以,使用基於記憶體限制的容器技術的服務應該多考慮自己記憶體使用的情況,尤其是記憶體超限之後的業務異常處理應該如何讓服務受影響的程度降到更低。在系統層次和應用層次一起努力,才能使記憶體隔離的效果達到最好。

記憶體資源審計

memory.memsw.usage_in_bytes:當前 cgroup 的記憶體+ swap 的使用量。

memory.usage_in_bytes:當前 cgroup 的記憶體使用量。

memory.max_usage_in_bytes:cgroup 最大的記憶體+ swap 的使用量。

memory.memsw.max_usage_in_bytes:cgroup 的最大記憶體使用量。

最後

Linux 的記憶體限制要說的就是這麼多了,當我們限制了記憶體之後,相對於使用實體機,實際上對於應用來說可用記憶體更少了,所以業務會相對更經常地暴露在記憶體資源緊張的狀態下。相對於虛擬機器( kvm , xen ),多個 cgroup 之間是共享核心的,我們可以從記憶體限制的角度思考一些關於“容器”技術相對於虛擬機器和實體機的很多特點:

  1. 記憶體更緊張,應用的記憶體洩漏會導致相對更嚴重的問題。
  2. 容器的生存週期時間更短,如果實體機的開機執行時間是以年計算的,那麼虛擬機器則是以月計算的,而容器應該跟程序的生存週期差不多,頂多以天為單位。所以,容器裡面要跑的應用應該可以被經常重啟。
  3. 當有多個 cgroup (容器)同時執行時,我們不能再以實體機或者虛擬機器對資源的使用的理解來規劃整體運營方式,我們需要更細節的理解什麼是 cache ,什麼是 swap ,什麼是共享記憶體,它們會被統計到哪些資源計數中?在核心並不衝突的環境,這些資源都是獨立給某一個業務使用的,在理解上即使不是很清晰,也不會造成歧義。但是在 cgroup 中,我們需要徹底理解這些細節,才能對遇到的情況進行預判,並規劃不同的處理策略。

也許我們還可以從中得到更多的理解,大家一起來想嘍?

由於字數限制,本文內容並不完整。完整內容請見 PDF 。

相關推薦

Linux 記憶體資源管理

Hi ,我是 Zorro 。這是我的微博地址,我會不定期在這裡更新文章,如果你有興趣,可以來關注我呦。 另外,我的其他聯絡方式: QQ: 30007147 本文PDF 在聊 cgroup 的記憶體限制之前,我們有必要先來講解一下: Linux 記憶體管理基礎知識 free

linux系統資源管理

ptime -a log class 進程信號 時間 物理內存 本地 打開 ps aux 查看所有進程 USER 該進程由那個用戶產生 PID 進程id %CPU 該進程占用CPU資源的百分比 %MEM 該進程占用物理內存的百分比 VSZ 該進程占用虛擬內存大小,單位KB

必須要注意的 C++ 動態記憶體資源管理(六)——vector的簡單實現

十六.myVector分析         我們知道,vector類將其元素存放在連續的記憶體中。為了獲得可接受的效能,vetor預先分配足夠大的記憶體來儲存可能需要的更多元素。vector的每個新增元素的成員函式會檢查是否有空間容納更多的元素。如果有,成員

linux記憶體碎片管理

1. 檢視記憶體碎片: # cat /proc/buddyinfo 2.跟蹤kernel記憶體有memory leak 在kernel裡開啟以下config: # memory leak debug CONFIG_SLAB=y CONFIG_DEBUG_SLAB=y CO

必須要注意的 C++ 動態記憶體資源管理(一)——視資源為物件

一.前言         所謂資源就是,一旦你用了它,將來必須還給系統。如果不這樣,糟糕的事情就會發生。C++ 程式中最常見使用的資源就是動態分配記憶體(如果你分配了記憶體卻忘記歸還它,就會導致記憶

【Oracle 12c 多租戶專題】PDB的記憶體資源管理

在12.2之前的版本,我們根本沒有辦法控制一個單獨的PDB能使用的記憶體總量。導致的結果就是“一個糟糕的鄰居”可能佔用大量記憶體從而導致同一個例項下其他PDB的效能下降。在Oracle 12.2中,你可以控制某單個PDB能使用的記憶體總量。 如果你的

必須要注意的 C++ 動態記憶體資源管理(三)——智慧指標

七.前言         在前面一節,我們簡單實現了三種類型資源的”指標物件”。其實在c++11的標準庫中已經為我們準備了這樣的指標物件——智慧指標,分別是:shared_ptr , unique_

鳥哥的Linux私房菜-----16、程序與資源管理

blog dsm alt 技術 article ack src mar data 鳥哥的Linux私房菜-----16、程序與資源管理

Linux硬件資源管理與外設設備使用、系統運行機制及用戶管理

ips cte tde lock type 設備文件 tar 所有 需要 Linux硬件資源管理   PCI設備 顯卡 $>>dmesg |grep -i vga[ 0.000000] Console: colour

Linux進程管理(二)進程的調度與資源限制

失敗 實用程序 代碼 協同 latin 中斷控制 可用 數值 無限 1 進程調度就緒進程最重要的特征是該進程是非阻塞的。進行用戶交互、大量讀寫文件、響應I/O和網絡事件的進程會花費大量時間來等待資源可用,在相當長的時間內無法轉為就緒狀態(長是相對於指令運行時間而言),因此就

Linux記憶體管理(最透徹的一篇)

摘要:本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理與使用者記憶體管理之

Linux的程序管理、檢視、殺死程序、任務管理、系統資源監控(課堂學習筆記)

  一、程序管理 1.檢視Linux啟動的第一個程序 2.檢視程序狀態 2.1觀察系統所有程式:ps aux 2.2檢視部分程序 2.3啟動httpd服務 2.4程序樹(可以檢視父程序與子程序) 二、檢視程序 1.Linux程序狀態 2.觀察程序

【轉】Linux記憶體管理

摘要:本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理與使用者記憶體管理之間的

linux記憶體管理的 夥伴系統和slab機制

夥伴系統 Linux核心中採用了一種同時適用於32位和64位系統的記憶體分頁模型,對於32位系統來說,兩級頁表足夠用了,而在x86_64系統中,用到了四級頁表。四級頁表分別為:  頁全域性目錄(Page Global Directory) 頁上級目錄(Page Upper Director

linux記憶體管理---虛擬地址 邏輯地址 線性地址 實體地址的區別(一)

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

linux ps 按程序消耗記憶體資源大小排序

linux ps 關於sort的解釋 --sort spec specify sorting order. Sorting syntax is [+|-]key[,[+|-]key[,...]] Choose a multi-letter key from the STA

Linux記憶體管理之malloc實現

程序虛擬地址空間由一個一個VMA來表示,這裡先接收VMA相關操作. 1.1 查詢VMA函式find_vma() struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr) 找到的vma結果需滿足條件:

記憶體管理linux記憶體頁面回收

一、概序:   在記憶體緊張時,核心會將很少使用的記憶體換出到交換分割槽,以便釋放出實體記憶體,此種機制成為“頁交換”, 也統稱為頁面回收,頁面回收涉及到LRU連結串列、記憶體回收演算法、Kswapd核心執行緒等知識,下面會做相關介紹。 二、LRU連結串列: 1、LRU連結串列:

Linux記憶體描述之記憶體節點node--Linux記憶體管理(二)

1 記憶體節點node 1.1 為什麼要用node來描述記憶體 這點前面是說的很明白了, NUMA結構下, 每個處理器CPU與一個本地記憶體直接相連, 而不同處理器之前則通過匯流排進行進一步的連線, 因此相對於任何一個CPU訪問本地記憶體的速度比訪問遠端記憶體的速度要快 Linux適用於各種不同的體系結

Linux記憶體描述之記憶體區域zone--Linux記憶體管理(三)

1 記憶體管理域zone 為了支援NUMA模型,也即CPU對不同記憶體單元的訪問時間可能不同,此時系統的實體記憶體被劃分為幾個節點(node), 一個node對應一個記憶體簇bank,即每個記憶體簇被認為是一個節點 首先, 記憶體被劃分為結點. 每個節點關聯到系統中的一個處理器, 核心中表示為pg_