linux內(nèi)存管理

轉(zhuǎn)載自:https://zhuanlan.zhihu.com/p/67059173,雖然不夠深入但是寫的很全,對于剛開始了解Linux內(nèi)存管理的很有幫助!

1. 掃盲篇

1.1 操作系統(tǒng)存儲層次

常見的計算機(jī)存儲層次如下:

  • 寄存器:CPU提供的,讀寫ns級別,容量字節(jié)級別。
  • CPU緩存:CPU和CPU間的緩存,讀寫10ns級別,容量較大一些,百到千節(jié)。
  • 主存:動態(tài)內(nèi)存,讀寫100ns級別,容量GB級別。
  • 外部存儲介質(zhì):磁盤、SSD,讀寫ms級別,容量可擴(kuò)展到TB級別。

CPU內(nèi)的緩存示意圖如下:

image

其中 L1d 和 L1i 都是CPU內(nèi)部的cache,

  • L1d 是數(shù)據(jù)cache。
  • L1i 是指令緩存。
  • L2是CPU內(nèi)部的,不區(qū)分指令和數(shù)據(jù)的。
  • 由于現(xiàn)代PC有多個CPU,L3緩存多個核心共用一個。

對于編程人員來說,絕大部分觀察主存和外部存儲介質(zhì)就可以了。如果要做極致的性能優(yōu)化,可以關(guān)注L1、L2、L3的cache,比如nginx的綁核操作、pthread調(diào)度會影響CPU cache等。

1.2 內(nèi)存管理概述

MMU(內(nèi)存管理單元):通過CPU將線性地址轉(zhuǎn)換成物理地址。

1.2.1 虛擬內(nèi)存

物理內(nèi)存是有限的(即使支持了熱插拔)、非連續(xù)的,不同的CPU架構(gòu)對物理內(nèi)存的組織都不同。這使得直接使用物理內(nèi)存非常復(fù)雜,為了降低使用內(nèi)存的復(fù)雜度,引入了虛擬內(nèi)存機(jī)制。

虛擬內(nèi)存抽象了應(yīng)用程序物理內(nèi)存的細(xì)節(jié),只允許物理內(nèi)存保存所需的信息(按需分頁),并提供了一種保護(hù)和控制進(jìn)程間數(shù)據(jù)共享數(shù)據(jù)的機(jī)制。有了虛擬內(nèi)存機(jī)制之后,每次訪問可以使用更易理解的虛擬地址,讓CPU轉(zhuǎn)換成實際的物理地址訪問內(nèi)存,降低了直接使用、管理物理內(nèi)存的門檻。

物理內(nèi)存按大小被分成頁框、頁,每塊物理內(nèi)存可以被映射為一個或多個虛擬內(nèi)存頁。這塊映射關(guān)系,由操作系統(tǒng)的頁表來保存,頁表是有層級的。層級最低的頁表,保存實際頁面的物理地址,較高層級的頁表包含指向低層級頁表的物理地址,指向頂級的頁表的地址,駐留在寄存器中。當(dāng)執(zhí)行地址轉(zhuǎn)換時,先從寄存器獲取頂級頁表地址,然后依次索引,找到具體頁面的物理地址。

1.2.2 大頁機(jī)制

虛擬地址轉(zhuǎn)換的過程中,需要好幾個內(nèi)存訪問,由于內(nèi)存訪問相對CPU較慢,為了提高性能,CPU維護(hù)了一個TLB地址轉(zhuǎn)換的cache,TLB是比較重要且珍稀的緩存,對于大內(nèi)存工作集的應(yīng)用程序,會因TLB命中率低大大影響到性能。

為了減少TLB的壓力,增加TLB緩存的命中率,有些系統(tǒng)會把頁的大小設(shè)為MB或者GB,這樣頁的數(shù)目少了,需要轉(zhuǎn)換的頁表項也小了,足以把虛擬地址和物理地址的映射關(guān)系,全部保存于TLB中。

1.2.3 區(qū)域概念

通常硬件會對訪問不同的物理內(nèi)存的范圍做出限制,在某些情況下設(shè)備無法對所有的內(nèi)存區(qū)域做DMA。在其他情況下,物理內(nèi)存的大小也會超過了虛擬內(nèi)存的最大可尋址大小,需要執(zhí)行特殊操作,才能訪問這些區(qū)域。這些情況下,Linux對內(nèi)存頁的可能使用情況將其分組到各自的區(qū)域中(方便管理和限制)。比如ZONE_DMA用于指明哪些可以用于DMA的區(qū)域,ZONE_HIGHMEM包含未永久映射到內(nèi)核地址空間的內(nèi)存,ZONE_NORMAL標(biāo)識正常的內(nèi)存區(qū)域。

1.2.4 節(jié)點

多核CPU的系統(tǒng)中,通常是NUMA系統(tǒng)(非統(tǒng)一內(nèi)存訪問系統(tǒng))。在這種系統(tǒng)中,內(nèi)存被安排成具有不同訪問延遲的存儲組,這取決于與處理器的距離。每一個庫,被稱為一個節(jié)點,每個節(jié)點Linux構(gòu)建了一個獨立的內(nèi)存管理子系統(tǒng)。一個節(jié)點有自己的區(qū)域集、可用頁和已用頁表和各種統(tǒng)計計數(shù)器。

1.2.5 page cache

從外部存儲介質(zhì)中加載數(shù)據(jù)到內(nèi)存中,這個過程是比較耗時的,因為外部存儲介質(zhì)讀寫性能毫秒級。為了減少外部存儲設(shè)備的讀寫,Linux內(nèi)核提供了Page cache。最常見的操作,每次讀取文件時,數(shù)據(jù)都會被放入頁面緩存中,以避免后續(xù)讀取時所進(jìn)行昂貴的磁盤訪問。同樣,當(dāng)寫入文件時,數(shù)據(jù)被重新放置在緩存中,被標(biāo)記為臟頁,定期的更新到存儲設(shè)備上,以提高讀寫性能。

1.2.6 匿名內(nèi)存

匿名內(nèi)存或者匿名映射表示不受文件系統(tǒng)支持的內(nèi)存,比如程序的堆棧隱式創(chuàng)立的,或者顯示通過mmap創(chuàng)立的。

1.2.7 內(nèi)存回收

貫穿系統(tǒng)的生命周期,一個物理頁可存儲不同類型的數(shù)據(jù),可以是內(nèi)核的數(shù)據(jù)結(jié)構(gòu),或是DMA訪問的buffer,或是從文件系統(tǒng)讀取的數(shù)據(jù),或是用戶程序分配的內(nèi)存等。

根據(jù)頁面的使用情況,Linux內(nèi)存管理對其進(jìn)行了不同的處理,可以隨時釋放的頁面,稱之為可回收頁面,這類頁面為:頁面緩存或者是匿名內(nèi)存(被再次交換到硬盤上)

大多數(shù)情況下,保存內(nèi)部內(nèi)核數(shù)據(jù)并用DMA緩沖區(qū)的頁面是不能重新被回收的,但是某些情況下,可以回收使用內(nèi)核數(shù)據(jù)結(jié)構(gòu)的頁面。例如:文件系統(tǒng)元數(shù)據(jù)的內(nèi)存緩存,當(dāng)系統(tǒng)處于內(nèi)存壓力情況下,可以從主存中丟棄它們。

釋放可回收的物理內(nèi)存頁的過程,被稱之為回收,可以同步或者異步的回收操作。當(dāng)系統(tǒng)負(fù)載增加到一定程序時,kswapd守護(hù)進(jìn)程會異步的掃描物理頁,可回收的物理頁被釋放,并逐出備份到存儲設(shè)備。

1.2.8 compaction

系統(tǒng)運行一段時間,內(nèi)存就會變得支離破碎。雖然使用虛擬村內(nèi)可以將分散的物理頁顯示為連續(xù)的物理頁,但有時需要分配較大的物理連續(xù)內(nèi)存區(qū)域。比如設(shè)備驅(qū)動程序需要一個用于DMA的大緩沖區(qū)時,或者大頁內(nèi)存機(jī)制分頁時。內(nèi)存compact可以解決了內(nèi)存碎片的問題,這個機(jī)制將被占用的頁面,從內(nèi)存區(qū)域合適的移動,以換取大塊的空閑物理頁的過程,由kcompactd守護(hù)進(jìn)程完成。

1.2.9 OOM killer

機(jī)器上的內(nèi)存可能會被耗盡,并且內(nèi)核將無法回收足夠的內(nèi)存用于運行新的程序,為了保存系統(tǒng)的其余部分,內(nèi)核會調(diào)用OOM killer殺掉一些進(jìn)程,以釋放內(nèi)存。

1.3 段頁機(jī)制簡介

段頁機(jī)制是操作系統(tǒng)管理內(nèi)存的一種方式,簡單的來說,就是如何管理、組織系統(tǒng)中的內(nèi)存。要理解這種機(jī)制,需要了解一下內(nèi)存尋址的發(fā)展歷程。

  • 直接尋址:早期的內(nèi)存很小,通過硬編碼的形式,直接定位到內(nèi)存地址。這種方式有著明顯的缺點:可控性弱、難以重定位、難以維護(hù)
  • 分段機(jī)制:8086處理器,尋址空間達(dá)到1MB,即地址線擴(kuò)展了20位,由于制作20位的寄存器較為困難,為了能在16位的寄存器的基礎(chǔ)上,尋址20位的地址空間,引入了的概念,即內(nèi)存地址=段基址左移4位+偏移
  • 分頁機(jī)制:隨著尋址空間的進(jìn)一步擴(kuò)大、虛擬內(nèi)存技術(shù)的引入,操作系統(tǒng)引入了分頁機(jī)制。引入分頁機(jī)制后,邏輯地址經(jīng)過段機(jī)制轉(zhuǎn)換得到的地址僅是中間地址,還需要通過頁機(jī)制轉(zhuǎn)換,才能得到實際的物理地址。邏輯地址 -->(分段機(jī)制) 線性地址 -->(分頁機(jī)制) 物理地址。

段頁機(jī)制詳見:https://blog.lecury.cn/2017/05/05/內(nèi)存尋址之段頁存儲機(jī)制分析/

2. 進(jìn)階篇

2.1 內(nèi)存分配

2.1.1 大塊內(nèi)存的分配

掃盲篇也提到,Linux基于段頁式機(jī)制管理物理內(nèi)存,內(nèi)存被分割成一個個頁框,由多級頁表管理。除此之外,由于硬件的約束:

  • DMA處理器,只能對RAM的前16MB尋址。
  • 32位機(jī)器CPU最大尋址空間,只有4GB,對于大容量超過4GB的RAM,無法訪問所有的地址空間。

Linux還將物理內(nèi)存劃分為不同的管理區(qū):ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM,每個管理區(qū)都有自己的描述符,也有自己的頁框分配器,示意圖如下:

image

對于連續(xù)頁框組的內(nèi)存分配請求,是由管理區(qū)分配器完成,每個管理區(qū)的頁框分配是通過伙伴系統(tǒng)算法來實現(xiàn)。內(nèi)核經(jīng)常請求和釋放單個頁框,為了提高性能,每個內(nèi)存管理區(qū),還定義了一個CPU頁框高速緩存,包含一些預(yù)選分配的頁框。

伙伴系統(tǒng)算法:內(nèi)核為分配一組連續(xù)的頁框而建立的一種健壯、高效的分配策略,這種策略緩解了內(nèi)存碎片的發(fā)生。算法的核心思想:是把所有的空閑頁框分組為11個塊鏈表,每個塊鏈表分別包含1、2、4、8、16、...、512、1024個連續(xù)頁框。舉個簡單的例子,說明算法的工作過程。

假設(shè)需要256個頁框的連續(xù)內(nèi)存,算法先在256個頁框的鏈表中,檢查是否還有空閑塊,如果有就分配出去。如果沒有,算法會找到下一個更大的512頁框的鏈表,如果存在空閑塊,內(nèi)核會把512頁框分割成兩部分,一半用來分配,另一半插入到256頁框的鏈表中。

2.1.2 小塊內(nèi)存的分配

伙伴系統(tǒng)算法采用頁框作為基本的內(nèi)存區(qū),這適合于大塊內(nèi)存的請求。對于小塊內(nèi)存的分配,是采用的slab分配器算法來實現(xiàn)的。slab并沒有脫離伙伴系統(tǒng)算法,而是基于伙伴系統(tǒng)分配的大內(nèi)存基礎(chǔ)上,進(jìn)一步細(xì)分小內(nèi)存對象的分配。slab 緩存分配器提供了很多優(yōu)點,

  • 首先,內(nèi)核通常依賴于對小對象的分配,它們會在系統(tǒng)生命周期內(nèi)進(jìn)行無數(shù)次分配,slab 緩存分配器通過對類似大小的對象進(jìn)行緩存,從而避免了常見的碎片問題。
  • slab 分配器還支持通用對象的初始化,從而避免了為同一目而對一個對象重復(fù)進(jìn)行初始化。
  • 最后slab 分配器還可以支持硬件緩存對齊和著色,這允許不同緩存中的對象占用相同的緩存行,從而提高緩存的利用率并獲得更好的性能。

slab分配器詳見:http://www.secretmango.com/jimb/Whitepapers/slabs/slab.html

備注: slab著色主要是為了更好的利用CPU L1 cache,所使用的地址偏移策略。如果slab分配對象后還有空間剩余,就會把剩余的空間進(jìn)行著色處理,盡可能將slab對象分散在L1不同的cache line中。

2.1.3 非連續(xù)內(nèi)存的分配

把內(nèi)存區(qū)映射到一組連續(xù)的頁框是最好的選擇,這樣會充分利用高速緩存。如果對內(nèi)存區(qū)的請求不是很頻繁,那么分配非連續(xù)的頁框,會是比較好的選擇,因為這樣會避免外部碎片,缺點是內(nèi)核的頁表比較亂。Linux以下方面使用了非連續(xù)內(nèi)存區(qū):

  • 為活動交換區(qū)分配數(shù)據(jù)結(jié)構(gòu)。
  • 給某些I/O驅(qū)動程序分配緩沖區(qū)。

2.2 實存、虛存

實存:進(jìn)程分配的、加載到主存中的內(nèi)存。包含來自共享庫的內(nèi)存,只要這些庫占用的頁框還在主存中,也包含所有正在使用的堆棧和堆內(nèi)存??梢酝ㄟ^ ps -o rss 查看進(jìn)程的實存大小。

虛存:包含進(jìn)程可以訪問的所有內(nèi)存,包含被換出、已經(jīng)分配但還未使用的內(nèi)存,以及來自共享庫的內(nèi)存??梢酝ㄟ^ ps -o vsz 查看進(jìn)程的虛存大小。

舉個例子,如果進(jìn)程A具有500K二進(jìn)制文件并且鏈接到2500K共享庫,則具有200K的堆棧/堆分配,其中100K實際上在內(nèi)存中(其余是交換或未使用),并且它實際上只加載了1000K的共享庫然后是400K自己的二進(jìn)制文件:

RSS: 400K + 1000K + 100K = 1500K
VSZ: 500K + 2500K + 200K = 3200K

實存和虛存是怎么轉(zhuǎn)換的呢?當(dāng)程序嘗試訪問的地址未處于實存中時,就發(fā)生頁面錯誤,操作系統(tǒng)必須以某種方式處理這種錯誤,從而使應(yīng)用程序正常運行。這些操作可以是:

  • 找到頁面駐留在磁盤上的位置,并加載到主存中。
  • 重新配置MMU,更新線性地址和物理地址的映射關(guān)系。
  • 等。

隨著進(jìn)程頁面錯誤的增長,主存中可用頁面越來越少,為了防止內(nèi)存完全耗盡,操作系統(tǒng)必須盡快釋放主存中暫時不用的頁面,以釋放空間供以后使用,方式如下:

  • 將修改后的頁面寫入到磁盤的專用區(qū)域上(調(diào)頁空間或者交換區(qū))。
  • 將未修改的頁面標(biāo)記為空閑(沒必要寫入磁盤,因為沒有被修改)。

調(diào)頁或者交換是操作系統(tǒng)的正常部分,需要注意的是過度交換,這表示當(dāng)前主存空間不足,頁面換出抖動對系統(tǒng)極為不利,會導(dǎo)致CPU和I/O負(fù)載升高,極端情況下,會造成操作系統(tǒng)所有的資源花費在調(diào)頁層面。

2.3 page cache

Linux中通過page cache機(jī)制來加速對磁盤文件的許多訪問,當(dāng)它首次讀取或?qū)懭霐?shù)據(jù)介質(zhì)時,Linux會將數(shù)據(jù)存儲在未使用的內(nèi)存區(qū)中,通過這些區(qū)域充當(dāng)緩存,如果再次讀取這些數(shù)據(jù)時,直接從內(nèi)存中快速獲取該數(shù)據(jù)。當(dāng)發(fā)生寫操作時,Linux不會立刻執(zhí)行磁盤寫操作,而是把page cache中的頁面標(biāo)記為臟頁,定期同步到存儲設(shè)備中。

可以通過free -m來查看page cache情況:

total       used       free     shared    buffers     cached
Mem:         32013      31288        724          0        241      12000
-/+ buffers/cache:      19046      12966
Swap:        32767      23134       9633

cached這列顯示了page cache的情況。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Linux 內(nèi)存管理 1 頁的概念 linux 內(nèi)核中把物理頁作為內(nèi)存分配的最小單位,32位CPU 頁的大小通常為...
    赤兔歡閱讀 3,418評論 0 5
  • 1. 用戶空間 通常 32 位 Linux 虛擬地址空間劃分, 0-3GB為用戶空間,3GB-4GB為內(nèi)核空間。每...
    Fly_Li閱讀 1,940評論 0 5
  • 在linux下,使用top,free等命令查看系統(tǒng)或者進(jìn)程的內(nèi)存使用情況時,經(jīng)??吹絙uff/cache meme...
    analanxingde閱讀 762評論 0 2
  • 在linux下我們經(jīng)常會使用到top,vmstat,free等命令查看系統(tǒng)或者進(jìn)程的內(nèi)存使用情況,經(jīng)常會看到buf...
    tracy_668閱讀 3,407評論 1 1
  • 二零一六年,農(nóng)歷十二月二十一,離春節(jié)還有九天,冒著嚴(yán)寒,我回到了離別了將近半年的老家。 寒風(fēng)獵獵作響,她依舊是穿著...
    小李子Viking閱讀 647評論 3 4

友情鏈接更多精彩內(nèi)容