RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

從全局的視角分析內(nèi)核對(duì)內(nèi)存的管理

Linux閱碼場(chǎng) ? 來源:未知 ? 作者:胡薇 ? 2018-05-14 14:24 ? 次閱讀

內(nèi)存溢出

內(nèi)存溢出的解決辦法:

1、等比例縮小圖片

2、對(duì)圖片采用軟引用,及時(shí)進(jìn)行 recycle( ) 操作。

3、使用加載圖片框架處理圖片,如專業(yè)處理圖片的 ImageLoader 圖片加載框架,還有XUtils 的 BitMapUtils 來處理。

這篇文章主要是分析了單個(gè)進(jìn)程空間的內(nèi)存布局與分配,是從全局的視角分析下內(nèi)核對(duì)內(nèi)存的管理;

下面主要從以下方面介紹 Linux 內(nèi)存管理:

進(jìn)程的內(nèi)存申請(qǐng)與分配;

內(nèi)存耗盡之后 OOM;

申請(qǐng)的內(nèi)存都在哪?

系統(tǒng)回收內(nèi)存;

1、進(jìn)程的內(nèi)存申請(qǐng)與分配

之前有篇文章介紹 hello world 程序是如何載入內(nèi)存以及是如何申請(qǐng)內(nèi)存的,我在這,再次說明下:同樣,還是先給出進(jìn)程的地址空間,我覺得對(duì)于任何開發(fā)人員這張圖是必須記住的,還有一張就是操作 disk ,memory 以及 cpu cache 的時(shí)間圖。

當(dāng)我們?cè)诮K端啟動(dòng)一個(gè)程序時(shí),終端進(jìn)程調(diào)用 exec 函數(shù)將可執(zhí)行文件載入內(nèi)存,此時(shí)代碼段,數(shù)據(jù)段,bbs 段,stack 段都通過 mmap 函數(shù)映射到內(nèi)存空間,堆則要根據(jù)是否有在堆上申請(qǐng)內(nèi)存來決定是否映射。

exec 執(zhí)行之后,此時(shí)并未真正開始執(zhí)行進(jìn)程,而是將 cpu 控制權(quán)交給了動(dòng)態(tài)鏈接庫裝載器,由它來將該進(jìn)程需要的動(dòng)態(tài)鏈接庫裝載進(jìn)內(nèi)存。之后才開始進(jìn)程的執(zhí)行,這個(gè)過程可以通過 strace 命令跟蹤進(jìn)程調(diào)用的系統(tǒng)函數(shù)來分析。

這是我上篇博客認(rèn)識(shí) pipe 中的程序,從這個(gè)輸出過程,可以看出和我上述描述的一致。

當(dāng)?shù)谝淮握{(diào)用 malloc 申請(qǐng)內(nèi)存時(shí),通過系統(tǒng)調(diào)用 brk 嵌入到內(nèi)核,首先會(huì)進(jìn)行一次判斷,是否有關(guān)于堆的 vma,如果沒有,則通過 mmap 匿名映射一塊內(nèi)存給堆,并建立 vma 結(jié)構(gòu),掛到 mm_struct 描述符上的紅黑樹和鏈表上。

然后回到用戶態(tài),通過內(nèi)存分配器(ptmaloc,tcmalloc,jemalloc)算法將分配到的內(nèi)存進(jìn)行管理,返回給用戶所需要的內(nèi)存。

如果用戶態(tài)申請(qǐng)大內(nèi)存時(shí),是直接調(diào)用 mmap 分配內(nèi)存,此時(shí)返回給用戶態(tài)的內(nèi)存還是虛擬內(nèi)存,直到第一次訪問返回的內(nèi)存時(shí),才真正進(jìn)行內(nèi)存的分配。

其實(shí)通過 brk 返回的也是虛擬內(nèi)存,但是經(jīng)過內(nèi)存分配器進(jìn)行切割分配之后(切割就必須訪問內(nèi)存),全都分配到了物理內(nèi)存

當(dāng)進(jìn)程在用戶態(tài)通過調(diào)用 free 釋放內(nèi)存時(shí),如果這塊內(nèi)存是通過 mmap 分配,則調(diào)用 munmap 直接返回給系統(tǒng)。

否則內(nèi)存是先返回給內(nèi)存分配器,然后由內(nèi)存分配器統(tǒng)一返還給系統(tǒng),這就是為什么當(dāng)我們調(diào)用 free 回收內(nèi)存之后,再次訪問這塊內(nèi)存時(shí),可能不會(huì)報(bào)錯(cuò)的原因。

當(dāng)然,當(dāng)整個(gè)進(jìn)程退出之后,這個(gè)進(jìn)程占用的內(nèi)存都會(huì)歸還給系統(tǒng)。

2、內(nèi)存耗盡之后OOM

在實(shí)習(xí)期間,有一臺(tái)測(cè)試機(jī)上的 mysql 實(shí)例經(jīng)常被 oom 殺死,OOM(out of memory)即為系統(tǒng)在內(nèi)存耗盡時(shí)的自我拯救措施,他會(huì)選擇一個(gè)進(jìn)程,將其殺死,釋放出內(nèi)存,很明顯,哪個(gè)進(jìn)程占用的內(nèi)存最多,即最可能被殺死,但事實(shí)是這樣的嗎?

今天早上去上班,剛好碰到了一起 OOM,突然發(fā)現(xiàn),OOM 一次,世界都安靜下來了,哈哈,測(cè)試機(jī)上的 redis 被殺死了。

OOM 關(guān)鍵文件 oom_kill.c,里面介紹了當(dāng)內(nèi)存不夠時(shí),系統(tǒng)如何選擇最應(yīng)該被殺死的進(jìn)程,選擇因素有挺多的,除了進(jìn)程占用的內(nèi)存外,還有進(jìn)程運(yùn)行的時(shí)間,進(jìn)程的優(yōu)先級(jí),是否為 root 用戶進(jìn)程,子進(jìn)程個(gè)數(shù)和占用內(nèi)存以及用戶控制參數(shù) oom_adj 都相關(guān)。

當(dāng)產(chǎn)生 oom 之后,函數(shù) select_bad_process 會(huì)遍歷所有進(jìn)程,通過之前提到的那些因素,每個(gè)進(jìn)程都會(huì)得到一個(gè) oom_score 分?jǐn)?shù),分?jǐn)?shù)最高,則被選為殺死的進(jìn)程。

我們可以通過設(shè)置 /proc//oom_adj 分?jǐn)?shù)來干預(yù)系統(tǒng)選擇殺死的進(jìn)程。

這是內(nèi)核關(guān)于這個(gè)oom_adj調(diào)整值的定義,最大可以調(diào)整為15,最小為-16,如果為-17,則該進(jìn)程就像買了vip會(huì)員一樣,不會(huì)被系統(tǒng)驅(qū)逐殺死了,因此,如果在一臺(tái)機(jī)器上有跑很多服務(wù)器,且你不希望自己的服務(wù)被殺死的話,就可以設(shè)置自己服務(wù)的 oom_adj 為-17。

當(dāng)然,說到這,就必須提到另一個(gè)參數(shù) /proc/sys/vm/overcommit_memory,man proc 說明如下:

意思就是當(dāng) overcommit_memory 為0時(shí),則為啟發(fā)式oom,即當(dāng)申請(qǐng)的虛擬內(nèi)存不是很夸張的大于物理內(nèi)存,則系統(tǒng)允許申請(qǐng),但是當(dāng)進(jìn)程申請(qǐng)的虛擬內(nèi)存很夸張的大于物理內(nèi)存,則就會(huì)產(chǎn)生 OOM。

例如只有8g的物理內(nèi)存,然后 redis 虛擬內(nèi)存占用了24G,物理內(nèi)存占用3g,如果這時(shí)執(zhí)行 bgsave,子進(jìn)程和父進(jìn)程共享物理內(nèi)存,但是虛擬內(nèi)存是自己的,即子進(jìn)程會(huì)申請(qǐng)24g的虛擬內(nèi)存,這很夸張大于物理內(nèi)存,就會(huì)產(chǎn)生一次OOM。

當(dāng) overcommit_memory 為1時(shí),則永遠(yuǎn)都允許 overmemory 內(nèi)存申請(qǐng),即不管你多大的虛擬內(nèi)存申請(qǐng)都允許,但是當(dāng)系統(tǒng)內(nèi)存耗盡時(shí),這時(shí)就會(huì)產(chǎn)生oom,即上述的redis例子,在 overcommit_memory=1 時(shí),是不會(huì)產(chǎn)生oom 的,因?yàn)槲锢韮?nèi)存足夠。

當(dāng) overcommit_memory 為2時(shí),永遠(yuǎn)都不能超出某個(gè)限定額的內(nèi)存申請(qǐng),這個(gè)限定額為 swap+RAM* 系數(shù)(/proc/sys/vm/overcmmit_ratio,默認(rèn)50%,可以自己調(diào)整),如果這么多資源已經(jīng)用光,那么后面任何嘗試申請(qǐng)內(nèi)存的行為都會(huì)返回錯(cuò)誤,這通常意味著此時(shí)沒法運(yùn)行任何新程序

以上就是 OOM 的內(nèi)容,了解原理,以及如何根據(jù)自己的應(yīng)用,合理的設(shè)置OOM。

3、系統(tǒng)申請(qǐng)的內(nèi)存都在哪?

我們了解了一個(gè)進(jìn)程的地址空間之后,是否會(huì)好奇,申請(qǐng)到的物理內(nèi)存都存在哪了?可能很多人覺得,不就是物理內(nèi)存嗎?

我這里說申請(qǐng)的內(nèi)存在哪,是因?yàn)槲锢韮?nèi)存有分為cache和普通物理內(nèi)存,可以通過 free 命令查看,而且物理內(nèi)存還有分 DMA,NORMAL,HIGH 三個(gè)區(qū),這里主要分析cache和普通內(nèi)存。

通過第一部分,我們知道一個(gè)進(jìn)程的地址空間幾乎都是 mmap 函數(shù)申請(qǐng),有文件映射和匿名映射兩種。

3.1 共享文件映射

我們先來看下代碼段和動(dòng)態(tài)鏈接庫映射段,這兩個(gè)都是屬于共享文件映射,也就是說由同一個(gè)可執(zhí)行文件啟動(dòng)的兩個(gè)進(jìn)程是共享這兩個(gè)段,都是映射到同一塊物理內(nèi)存,那么這塊內(nèi)存在哪了?我寫了個(gè)程序測(cè)試如下:

我們先看下當(dāng)前系統(tǒng)的內(nèi)存使用情況:

當(dāng)我在本地新建一個(gè)1G的文件:

dd if=/dev/zero of=fileblock bs=M count=1024

然后調(diào)用上述程序,進(jìn)行共享文件映射,此時(shí)內(nèi)存使用情況為:

我們可以發(fā)現(xiàn),buff/cache 增長(zhǎng)了大概1G,因此我們可以得出結(jié)論,代碼段和動(dòng)態(tài)鏈接庫段是映射到內(nèi)核cache中,也就是說當(dāng)執(zhí)行共享文件映射時(shí),文件是先被讀取到 cache 中,然后再映射到用戶進(jìn)程空間中。

3.2 私有文件映射段

對(duì)于進(jìn)程空間中的數(shù)據(jù)段,其必須是私有文件映射,因?yàn)槿绻枪蚕砦募成?,那么同一個(gè)可執(zhí)行文件啟動(dòng)的兩個(gè)進(jìn)程,任何一個(gè)進(jìn)程修改數(shù)據(jù)段,都將影響另一個(gè)進(jìn)程了,我將上述測(cè)試程序改寫成匿名文件映射:

在執(zhí)行程序執(zhí)行,需要先將之前的 cache 釋放掉,否則會(huì)影響結(jié)果

echo 1 >> /proc/sys/vm/drop_caches

接著執(zhí)行程序,看下內(nèi)存使用情況:

從使用前和使用后對(duì)比,可以發(fā)現(xiàn) used 和 buff/cache 分別增長(zhǎng)了1G,說明當(dāng)進(jìn)行私有文件映射時(shí),首先是將文件映射到 cache 中,然后如果某個(gè)文件對(duì)這個(gè)文件進(jìn)行修改,則會(huì)從其他內(nèi)存中分配一塊內(nèi)存先將文件數(shù)據(jù)拷貝至新分配的內(nèi)存,然后再在新分配的內(nèi)存上進(jìn)行修改,這也就是寫時(shí)復(fù)制。

這也很好理解,因?yàn)槿绻粋€(gè)可執(zhí)行文件開啟多個(gè)實(shí)例,那么內(nèi)核先將這個(gè)可執(zhí)行的數(shù)據(jù)段映射到 cache,然后每個(gè)實(shí)例如果有修改數(shù)據(jù)段,則都將分配一個(gè)一塊內(nèi)存存儲(chǔ)數(shù)據(jù)段,畢竟數(shù)據(jù)段也是一個(gè)進(jìn)程私有的。

通過上述分析,可以得出結(jié)論,如果是文件映射,則都是將文件映射到 cache 中,然后根據(jù)共享還是私有進(jìn)行不同的操作。

3.3 私有匿名映射

像 bbs 段,堆,棧這些都是匿名映射,因?yàn)榭蓤?zhí)行文件中沒有相應(yīng)的段,而且必須是私有映射,否則如果當(dāng)前進(jìn)程 fork 出一個(gè)子進(jìn)程,那么父子進(jìn)程將會(huì)共享這些段,一個(gè)修改都會(huì)影響到彼此,這是不合理的。

ok,現(xiàn)在我把上述測(cè)試程序改成私有匿名映射

這時(shí)再來看下內(nèi)存的使用情況

我們可以看到,只有 used 增加了1G,而 buff/cache 并沒有增長(zhǎng);說明,在進(jìn)行匿名私有映射時(shí),并沒有占用 cache,其實(shí)這也是有道理,因?yàn)榫椭挥挟?dāng)前進(jìn)程在使用這塊這塊內(nèi)存,沒有必要占用寶貴的 cache。

3.4 共享匿名映射

當(dāng)我們需要在父子進(jìn)程共享內(nèi)存時(shí),就可以用到 mmap 共享匿名映射,那么共享匿名映射的內(nèi)存是存放在哪了?我繼續(xù)改寫上述測(cè)試程序?yàn)楣蚕砟涿成?。

這時(shí)來看下內(nèi)存的使用情況:

從上述結(jié)果,我們可以看出,只有buff/cache增長(zhǎng)了1G,即當(dāng)進(jìn)行共享匿名映射時(shí),這時(shí)是從 cache 中申請(qǐng)內(nèi)存,道理也很明顯,因?yàn)楦缸舆M(jìn)程共享這塊內(nèi)存,共享匿名映射存在于 cache,然后每個(gè)進(jìn)程再映射到彼此的虛存空間,這樣即可操作的是同一塊內(nèi)存。

4、系統(tǒng)回收內(nèi)存

當(dāng)系統(tǒng)內(nèi)存不足時(shí),有兩種方式進(jìn)行內(nèi)存釋放,一種是手動(dòng)的方式,另一種是系統(tǒng)自己觸發(fā)的內(nèi)存回收,先來看下手動(dòng)觸發(fā)方式。

4.1 手動(dòng)回收內(nèi)存

手動(dòng)回收內(nèi)存,之前也有演示過,即

echo 1 >> /proc/sys/vm/drop_caches

我們可以在 man proc 下面看到關(guān)于這個(gè)的簡(jiǎn)介

從這個(gè)介紹可以看出,當(dāng) drop_caches 文件為1時(shí),這時(shí)將釋放 pagecache 中可釋放的部分(有些 cache 是不能通過這個(gè)釋放的),當(dāng) drop_caches 為2時(shí),這時(shí)將釋放 dentries 和 inodes 緩存,當(dāng) drop_caches 為3時(shí),這同時(shí)釋放上述兩項(xiàng)。

關(guān)鍵還有最后一句,意思是說如果 pagecache 中有臟數(shù)據(jù)時(shí),操作 drop_caches 是不能釋放的,必須通過 sync 命令將臟數(shù)據(jù)刷新到磁盤,才能通過操作 drop_caches 釋放 pagecache。

ok,之前有提到有些pagecache是不能通過drop_caches釋放的,那么除了上述提文件映射和共享匿名映射外,還有有哪些東西是存在pagecache了?

4.2 tmpfs

我們先來看下 tmpfs ,tmpfs 和 procfs,sysfs 以及 ramfs 一樣,都是基于內(nèi)存的文件系統(tǒng),tmpfs 和 ramfs 的區(qū)別就是 ramfs 的文件基于純內(nèi)存的,和 tmpfs 除了純內(nèi)存外,還會(huì)使用 swap 交換空間,以及 ramfs 可能會(huì)把內(nèi)存耗盡,而 tmpfs 可以限定使用內(nèi)存大小,可以用命令 df -T -h 查看系統(tǒng)一些文件系統(tǒng),其中就有一些是 tmpfs,比較出名的是目錄 /dev/shm

tmpfs 文件系統(tǒng)源文件在內(nèi)核源碼 mm/shmem.c,tmpfs實(shí)現(xiàn)很復(fù)雜,之前有介紹虛擬文件系統(tǒng),基于 tmpfs 文件系統(tǒng)創(chuàng)建文件和其他基于磁盤的文件系統(tǒng)一樣,也會(huì)有 inode,super_block,identry,file 等結(jié)構(gòu),區(qū)別主要是在讀寫上,因?yàn)樽x寫才涉及到文件的載體是內(nèi)存還是磁盤。

而 tmpfs 文件的讀函數(shù) shmem_file_read,過程主要為通過 inode 結(jié)構(gòu)找到 address_space 地址空間,其實(shí)就是磁盤文件的 pagecache,然后通過讀偏移定位cache 頁以及頁內(nèi)偏移。

這時(shí)就可以直接從這個(gè) pagecache 通過函數(shù) __copy_to_user 將緩存頁內(nèi)數(shù)據(jù)拷貝到用戶空間,當(dāng)我們要讀物的數(shù)據(jù)不pagecache中時(shí),這時(shí)要判斷是否在 swap 中,如果在則先將內(nèi)存頁 swap in,再讀取。

tmpfs 文件的寫函數(shù) shmem_file_write,過程主要為先判斷要寫的頁是否在內(nèi)存中,如果在,則直接將用戶態(tài)數(shù)據(jù)通過函數(shù)__copy_from_user拷貝至內(nèi)核pagecache中覆蓋老數(shù)據(jù),并標(biāo)為 dirty。

如果要寫的數(shù)據(jù)不再內(nèi)存中,則判斷是否在swap 中,如果在,則先讀取出來,用新數(shù)據(jù)覆蓋老數(shù)據(jù)并標(biāo)為臟,如果即不在內(nèi)存也不在磁盤,則新生成一個(gè) pagecache 存儲(chǔ)用戶數(shù)據(jù)。

由上面分析,我們知道基于 tmpfs 的文件也是使用 cache 的,我們可以在/dev/shm上創(chuàng)建一個(gè)文件來檢測(cè)下:

看到了吧,cache 增長(zhǎng)了1G,驗(yàn)證了 tmpfs 的確使用的 cache 內(nèi)存。

其實(shí) mmap 匿名映射原理也是用了 tmpfs,在 mm/mmap.c->do_mmap_pgoff 函數(shù)內(nèi)部,有判斷如果 file 結(jié)構(gòu)為空以及為 SHARED 映射,則調(diào)用 shmem_zero_setup(vma) 函數(shù)在 tmpfs 上用新建一個(gè)文件

這里就解釋了為什么共享匿名映射內(nèi)存初始化為0了,但是我們知道用 mmap 分配的內(nèi)存初始化為0,就是說 mmap 私有匿名映射也為0,那么體現(xiàn)在哪了?

這個(gè)在 do_mmap_pgoff 函數(shù)內(nèi)部可沒有體現(xiàn)出來,而是在缺頁異常,然后分配一種特殊的初始化為0的頁。

那么這個(gè) tmpfs 占有的內(nèi)存頁可以回收嗎?

也就是說 tmpfs 文件占有的 pagecache 是不能回收的,道理也很明顯,因?yàn)橛形募眠@些頁,就不能回收。

4.3 共享內(nèi)存

posix 共享內(nèi)存其實(shí)和 mmap 共享映射是同一個(gè)道理,都是利用在 tmpfs 文件系統(tǒng)上新建一個(gè)文件,然后再映射到用戶態(tài),最后兩個(gè)進(jìn)程操作同一個(gè)物理內(nèi)存,那么 System V 共享內(nèi)存是否也是利用 tmpfs 文件系統(tǒng)了?

我們可以跟蹤到下述函數(shù)

這個(gè)函數(shù)就是新建一個(gè)共享內(nèi)存段,其中函數(shù)

shmem_kernel_file_setup

就是在 tmpfs 文件系統(tǒng)上創(chuàng)建一個(gè)文件,然后通過這個(gè)內(nèi)存文件實(shí)現(xiàn)進(jìn)程通信,這我就不寫測(cè)試程序了,而且這也是不能回收的,因?yàn)楣蚕韮?nèi)存ipc機(jī)制生命周期是隨內(nèi)核的,也就是說你創(chuàng)建共享內(nèi)存之后,如果不顯示刪除的話,進(jìn)程退出之后,共享內(nèi)存還是存在的。

之前看了一些技術(shù)博客,說到 Poxic 和 System V 兩套 ipc 機(jī)制(消息隊(duì)列,信號(hào)量以及共享內(nèi)存)都是使用 tmpfs 文件系統(tǒng),也就是說最終內(nèi)存使用的都是 pagecache,但是我在源碼中看出了兩個(gè)共享內(nèi)存是基于 tmpfs 文件系統(tǒng),其他信號(hào)量和消息隊(duì)列還沒看出來(有待后續(xù)考究)。

posix 消息隊(duì)列的實(shí)現(xiàn)有點(diǎn)類似與 pipe 的實(shí)現(xiàn),也是自己一套 mqueue 文件系統(tǒng),然后在 inode 上的 i_private 上掛上關(guān)于消息隊(duì)列屬性 mqueue_inode_info,在這個(gè)屬性上,內(nèi)核2.6時(shí),是用一個(gè)數(shù)組存儲(chǔ)消息,而到了4.6則用紅黑樹了存儲(chǔ)消息(我下載了這兩個(gè)版本,具體什么時(shí)候開始用紅黑樹,沒深究)。

然后兩個(gè)進(jìn)程每次操作都是操作這個(gè) mqueue_inode_info 中的消息數(shù)組或者紅黑樹,實(shí)現(xiàn)進(jìn)程通信,和這個(gè) mqueue_inode_info 類似的還有 tmpfs 文件系統(tǒng)屬性shmem_inode_info 和為epoll服務(wù)的文件系統(tǒng) eventloop,也有一個(gè)特殊屬性struct eventpoll,這個(gè)是掛在 file 結(jié)構(gòu)的 private_data 等等。

說到這,可以小結(jié)下,進(jìn)程空間中代碼段,數(shù)據(jù)段,動(dòng)態(tài)鏈接庫(共享文件映射),mmap 共享匿名映射都存在于 cache 中,但是這些內(nèi)存頁都有被進(jìn)程引用,所以是不能釋放的,基于 tmpfs 的 ipc 進(jìn)程間通信機(jī)制的生命周期是隨內(nèi)核,因此也是不能通過 drop_caches 釋放。

雖然上述提及的cache不能釋放,但是后面有提到,當(dāng)內(nèi)存不足時(shí),這些內(nèi)存是可以 swap out 的。

因此 drop_caches 能釋放的就是當(dāng)從磁盤讀取文件時(shí)的緩存頁以及某個(gè)進(jìn)程將某個(gè)文件映射到內(nèi)存之后,進(jìn)程退出,這時(shí)映射文件的的緩存頁如果沒有被引用,也是可以被釋放的。

4.4 內(nèi)存自動(dòng)釋放方式

當(dāng)系統(tǒng)內(nèi)存不夠時(shí),操作系統(tǒng)有一套自我整理內(nèi)存,并盡可能的釋放內(nèi)存機(jī)制,如果這套機(jī)制不能釋放足夠多的內(nèi)存,那么只能 OOM 了。

之前在提及OOM時(shí),說道 redis 因?yàn)镺OM被殺死,如下:

第二句后半部分,

total-vm:186660kB, anon-rss:9388kB, file-rss:4kB

把一個(gè)進(jìn)程內(nèi)存使用情況,用三個(gè)屬性進(jìn)行了說明,即所有虛擬內(nèi)存,常駐內(nèi)存匿名映射頁以及常駐內(nèi)存文件映射頁。

其實(shí)從上述的分析,我們也可以知道一個(gè)進(jìn)程其實(shí)就是文件映射和匿名映射:

文件映射:代碼段,數(shù)據(jù)段,動(dòng)態(tài)鏈接庫共享存儲(chǔ)段以及用戶程序的文件映射段;

匿名映射:bbs段,堆,以及當(dāng) malloc 用 mmap 分配的內(nèi)存,還有mmap共享內(nèi)存段;

其實(shí)內(nèi)核回收內(nèi)存就是根據(jù)文件映射和匿名映射來進(jìn)行的,在 mmzone.h 有如下定義:

LRU_UNEVICTABLE 即為不可驅(qū)逐頁 lru,我的理解就是當(dāng)調(diào)用 mlock 鎖住內(nèi)存,不讓系統(tǒng) swap out 出去的頁列表。

簡(jiǎn)單說下 linux 內(nèi)核自動(dòng)回收內(nèi)存原理,內(nèi)核有一個(gè) kswapd 會(huì)周期性的檢查內(nèi)存使用情況,如果發(fā)現(xiàn)空閑內(nèi)存定于 pages_low,則 kswapd 會(huì)對(duì) lru_list 前四個(gè) lru 隊(duì)列進(jìn)行掃描,在活躍鏈表中查找不活躍的頁,并添加不活躍鏈表。

然后再遍歷不活躍鏈表,逐個(gè)進(jìn)行回收釋放出32個(gè)頁,知道 free page 數(shù)量達(dá)到 pages_high,針對(duì)不同的頁,回收方式也不一樣。

當(dāng)然,當(dāng)內(nèi)存水平低于某個(gè)極限閾值時(shí),會(huì)直接發(fā)出內(nèi)存回收,原理和 kswapd 一樣,但是這次回收力度更大,需要回收更多的內(nèi)存。

文件頁:

如果是臟頁,則直接回寫進(jìn)磁盤,再回收內(nèi)存。

如果不是臟頁,則直接釋放回收,因?yàn)槿绻莍o讀緩存,直接釋放掉,下次讀時(shí),缺頁異常,直接到磁盤讀回來即可,如果是文件映射頁,直接釋放掉,下次訪問時(shí),也是產(chǎn)生兩個(gè)缺頁異常,一次將文件內(nèi)容讀取進(jìn)磁盤,另一次與進(jìn)程虛擬內(nèi)存關(guān)聯(lián)。

匿名頁: 因?yàn)槟涿摏]有回寫的地方,如果釋放掉,那么就找不到數(shù)據(jù)了,所以匿名頁的回收是采取 swap out 到磁盤,并在頁表項(xiàng)做個(gè)標(biāo)記,下次缺頁異常在從磁盤 swap in 進(jìn)內(nèi)存。

swap 換進(jìn)換出其實(shí)是很占用系統(tǒng)IO的,如果系統(tǒng)內(nèi)存需求突然間迅速增長(zhǎng),那么cpu 將被io占用,系統(tǒng)會(huì)卡死,導(dǎo)致不能對(duì)外提供服務(wù),因此系統(tǒng)提供一個(gè)參數(shù),用于設(shè)置當(dāng)進(jìn)行內(nèi)存回收時(shí),執(zhí)行回收 cache 和 swap 匿名頁的,這個(gè)參數(shù)為:

意思就是說這個(gè)值越高,越可能使用 swap 的方式回收內(nèi)存,最大值為100,如果設(shè)為0,則盡可能使用回收 cache 的方式釋放內(nèi)存。

5、總結(jié)

這篇文章主要是寫了 linux 內(nèi)存管理相關(guān)的東西:

首先是回顧了進(jìn)程地址空間;

其次當(dāng)進(jìn)程消耗大量?jī)?nèi)存而導(dǎo)致內(nèi)存不足時(shí),我們可以有兩種方式:第一是手動(dòng)回收 cache;另一種是系統(tǒng)后臺(tái)線程 swapd 執(zhí)行內(nèi)存回收工作。

最后當(dāng)申請(qǐng)的內(nèi)存大于系統(tǒng)剩余的內(nèi)存時(shí),這時(shí)就只會(huì)產(chǎn)生 OOM,殺死進(jìn)程,釋放內(nèi)存,從這個(gè)過程,可以看出系統(tǒng)為了騰出足夠的內(nèi)存,是多么的努力啊。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11292

    瀏覽量

    209325
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3019

    瀏覽量

    74003

原文標(biāo)題:77%的Linux運(yùn)維都不懂的內(nèi)核問題

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux kernel內(nèi)存管理模塊結(jié)構(gòu)分析

    基于上面章節(jié)的需求,Linux kernel虛擬內(nèi)存(VM)、DMA mapping以及DMA buffer sharing三個(gè)角度,對(duì)內(nèi)存進(jìn)行管理.
    發(fā)表于 09-19 11:55 ?1770次閱讀
    Linux kernel<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>模塊結(jié)構(gòu)<b class='flag-5'>分析</b>

    Linux的內(nèi)存管理是什么,Linux的內(nèi)存管理詳解

    Linux的內(nèi)存管理 Linux的內(nèi)存管理是一個(gè)非常復(fù)雜的過程,主要分成兩個(gè)大的部分:內(nèi)核內(nèi)存
    的頭像 發(fā)表于 05-11 17:54 ?6039次閱讀
    Linux的<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>是什么,Linux的<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>詳解

    RT-Thread內(nèi)核對(duì)象管理介紹

    繼續(xù)研究RT-Thread 最基礎(chǔ),最重要的概念:內(nèi)核對(duì)象。
    發(fā)表于 06-02 09:48 ?421次閱讀
    RT-Thread<b class='flag-5'>內(nèi)核對(duì)象管理</b>介紹

    Linux內(nèi)核內(nèi)存管理架構(gòu)解析

    的要求。本文內(nèi)存管理硬件架構(gòu)、地址空間劃分和內(nèi)存管理軟件架構(gòu)三個(gè)方面入手,嘗試對(duì)內(nèi)存
    的頭像 發(fā)表于 01-04 09:24 ?653次閱讀
    Linux<b class='flag-5'>內(nèi)核</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>架構(gòu)解析

    鴻蒙內(nèi)核源碼分析(內(nèi)存管理篇):虛擬內(nèi)存和物理內(nèi)存是怎么管理

    有了上篇鴻蒙內(nèi)核源碼分析(內(nèi)存概念篇)的基礎(chǔ),本篇講內(nèi)存管理部分,本章源碼超級(jí)多,很燒腦,但筆者關(guān)鍵處都加了注釋。廢話不多說,開始吧。初始化
    發(fā)表于 11-20 10:54

    RT-Thread內(nèi)核學(xué)習(xí)資料匯總

    閱讀分析,保證名字唯一性。修改方法如下:  2、RT-Thread內(nèi)核對(duì)象rt_object介紹  內(nèi)核對(duì)象控制塊  對(duì)象(object)結(jié)構(gòu)體 3、RT-Thread內(nèi)核對(duì)象管理AP
    發(fā)表于 03-15 10:45

    RT-Thread系統(tǒng)內(nèi)核對(duì)象管理接口包括哪些?對(duì)象之間有何關(guān)系呢

    內(nèi)核中絕大部分設(shè)施, 這些內(nèi)核對(duì)象可以是靜態(tài)分配的靜態(tài)對(duì)象,也可以是系統(tǒng)內(nèi)存堆中分配的動(dòng)態(tài)對(duì)象。RT-Thread內(nèi)核對(duì)象包括:線程,信
    發(fā)表于 08-25 15:23

    RT_Thread文檔—內(nèi)核對(duì)象模型-靜態(tài)對(duì)象與動(dòng)態(tài)對(duì)象存儲(chǔ)位置疑問求解

    在文檔學(xué)習(xí)中看到其對(duì)靜態(tài)內(nèi)核對(duì)象與動(dòng)態(tài)對(duì)象描述:“內(nèi)核對(duì)象分為兩類:靜態(tài)內(nèi)核對(duì)象和動(dòng)態(tài)內(nèi)核對(duì)象,靜態(tài)內(nèi)核對(duì)象通常放在 RW 段和 ZI 段中
    發(fā)表于 03-14 10:28

    VxWorks內(nèi)存管理機(jī)制的分析與研究

    實(shí)時(shí)性、可靠性是嵌入式開發(fā)對(duì)內(nèi)存管理的基本要求,本文探討了操作系統(tǒng)內(nèi)存管理的主要問題,對(duì)嵌入式操作系統(tǒng)Vxworks 的內(nèi)存
    發(fā)表于 01-07 12:35 ?23次下載

    華為物聯(lián)網(wǎng)操作系統(tǒng)LiteOS內(nèi)核教程06-內(nèi)存管理

    1. LiteOS內(nèi)核內(nèi)存管理 1.1. 內(nèi)存管理 在系統(tǒng)運(yùn)行的過程中,一些內(nèi)存空間大小是不確
    發(fā)表于 03-13 16:02 ?1803次閱讀

    鴻蒙內(nèi)核源碼分析: 虛擬內(nèi)存和物理內(nèi)存是怎么管理

    有了上篇鴻蒙內(nèi)核源碼分析(內(nèi)存概念篇)的基礎(chǔ),本篇講內(nèi)存管理部分,本章源碼超級(jí)多,很燒腦,但筆者關(guān)鍵處都加了注釋。廢話不多說,開始吧。
    發(fā)表于 11-23 11:45 ?19次下載
    鴻蒙<b class='flag-5'>內(nèi)核</b>源碼<b class='flag-5'>分析</b>: 虛擬<b class='flag-5'>內(nèi)存</b>和物理<b class='flag-5'>內(nèi)存</b>是怎么<b class='flag-5'>管理</b>的

    RT-Thread 內(nèi)核學(xué)習(xí)筆記 - 內(nèi)核對(duì)象鏈表結(jié)構(gòu)深入理解

    RT-Thread 內(nèi)核學(xué)習(xí)筆記 - 內(nèi)核對(duì)象rt_objectRT-Thread 內(nèi)核學(xué)習(xí)筆記 - 內(nèi)核對(duì)象管理RT-Thread 內(nèi)核學(xué)
    發(fā)表于 01-25 18:23 ?6次下載
    RT-Thread <b class='flag-5'>內(nèi)核</b>學(xué)習(xí)筆記 - <b class='flag-5'>內(nèi)核對(duì)</b>象鏈表結(jié)構(gòu)深入理解

    RT-Thread 內(nèi)核學(xué)習(xí)筆記 - 內(nèi)核對(duì)象初始化鏈表組織方式

    RT-Thread 內(nèi)核學(xué)習(xí)筆記 - 內(nèi)核對(duì)象rt_objectRT-Thread 內(nèi)核學(xué)習(xí)筆記 - 內(nèi)核對(duì)象管理RT-Thread 內(nèi)核學(xué)
    發(fā)表于 01-25 18:24 ?3次下載
    RT-Thread <b class='flag-5'>內(nèi)核</b>學(xué)習(xí)筆記 - <b class='flag-5'>內(nèi)核對(duì)</b>象初始化鏈表組織方式

    RT-Thread 內(nèi)核學(xué)習(xí)筆記 - 內(nèi)核對(duì)象操作API

    RT-Thread 內(nèi)核學(xué)習(xí)筆記 - 內(nèi)核對(duì)象rt_objectRT-Thread 內(nèi)核學(xué)習(xí)筆記 - 內(nèi)核對(duì)象管理RT-Thread 內(nèi)核學(xué)
    發(fā)表于 01-25 18:26 ?7次下載
    RT-Thread <b class='flag-5'>內(nèi)核</b>學(xué)習(xí)筆記 - <b class='flag-5'>內(nèi)核對(duì)</b>象操作API

    如何有效地內(nèi)核中訪問設(shè)備的全局內(nèi)存

    個(gè)用途是將多維數(shù)組的 2D 塊以合并的方式全局內(nèi)存提取到共享內(nèi)存中,然后讓連續(xù)的線程跨過共享內(nèi)存塊。與全局內(nèi)存不同,對(duì)共享
    的頭像 發(fā)表于 04-11 10:07 ?1198次閱讀
    如何有效地<b class='flag-5'>從</b><b class='flag-5'>內(nèi)核</b>中訪問設(shè)備的<b class='flag-5'>全局內(nèi)存</b>
    RM新时代网站-首页