linux内存管理_浅谈Linux内存管理
1. 掃盲篇
1.1 操作系統(tǒng)存儲層次
常見的計算機存儲層次如下:
- 寄存器:CPU提供的,讀寫ns級別,容量字節(jié)級別。
- CPU緩存:CPU和CPU間的緩存,讀寫10ns級別,容量較大一些,百到千節(jié)。
- 主存:動態(tài)內(nèi)存,讀寫100ns級別,容量GB級別。
- 外部存儲介質(zhì):磁盤、SSD,讀寫ms級別,容量可擴展到TB級別。
CPU內(nèi)的緩存示意圖如下:
其中 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)存非常復雜,為了降低使用內(nèi)存的復雜度,引入了虛擬內(nèi)存機制。
虛擬內(nèi)存抽象了應用程序物理內(nèi)存的細節(jié),只允許物理內(nèi)存保存所需的信息(按需分頁),并提供了一種保護和控制進程間數(shù)據(jù)共享數(shù)據(jù)的機制。有了虛擬內(nèi)存機制之后,每次訪問可以使用更易理解的虛擬地址,讓CPU轉(zhuǎn)換成實際的物理地址訪問內(nèi)存,降低了直接使用、管理物理內(nèi)存的門檻。
物理內(nèi)存按大小被分成頁框、頁,每塊物理內(nèi)存可以被映射為一個或多個虛擬內(nèi)存頁。這塊映射關(guān)系,由操作系統(tǒng)的頁表來保存,頁表是有層級的。層級最低的頁表,保存實際頁面的物理地址,較高層級的頁表包含指向低層級頁表的物理地址,指向頂級的頁表的地址,駐留在寄存器中。當執(zhí)行地址轉(zhuǎn)換時,先從寄存器獲取頂級頁表地址,然后依次索引,找到具體頁面的物理地址。
1.2.2 大頁機制
虛擬地址轉(zhuǎn)換的過程中,需要好幾個內(nèi)存訪問,由于內(nèi)存訪問相對CPU較慢,為了提高性能,CPU維護了一個TLB地址轉(zhuǎn)換的cache,TLB是比較重要且珍稀的緩存,對于大內(nèi)存工作集的應用程序,會因TLB命中率低大大影響到性能。
為了減少TLB的壓力,增加TLB緩存的命中率,有些系統(tǒng)會把頁的大小設為MB或者GB,這樣頁的數(shù)目少了,需要轉(zhuǎn)換的頁表項也小了,足以把虛擬地址和物理地址的映射關(guān)系,全部保存于TLB中。
1.2.3 區(qū)域概念
通常硬件會對訪問不同的物理內(nèi)存的范圍做出限制,在某些情況下設備無法對所有的內(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標識正常的內(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ì)讀寫性能毫秒級。為了減少外部存儲設備的讀寫,Linux內(nèi)核提供了Page cache。最常見的操作,每次讀取文件時,數(shù)據(jù)都會被放入頁面緩存中,以避免后續(xù)讀取時所進行昂貴的磁盤訪問。同樣,當寫入文件時,數(shù)據(jù)被重新放置在緩存中,被標記為臟頁,定期的更新到存儲設備上,以提高讀寫性能。
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)存管理對其進行了不同的處理,可以隨時釋放的頁面,稱之為可回收頁面,這類頁面為:頁面緩存或者是匿名內(nèi)存(被再次交換到硬盤上)
大多數(shù)情況下,保存內(nèi)部內(nèi)核數(shù)據(jù)并用DMA緩沖區(qū)的頁面是不能重新被回收的,但是某些情況下,可以回收使用內(nèi)核數(shù)據(jù)結(jié)構(gòu)的頁面。例如:文件系統(tǒng)元數(shù)據(jù)的內(nèi)存緩存,當系統(tǒng)處于內(nèi)存壓力情況下,可以從主存中丟棄它們。
釋放可回收的物理內(nèi)存頁的過程,被稱之為回收,可以同步或者異步的回收操作。當系統(tǒng)負載增加到一定程序時,kswapd守護進程會異步的掃描物理頁,可回收的物理頁被釋放,并逐出備份到存儲設備。
1.2.8 compaction
系統(tǒng)運行一段時間,內(nèi)存就會變得支離破碎。雖然使用虛擬村內(nèi)可以將分散的物理頁顯示為連續(xù)的物理頁,但有時需要分配較大的物理連續(xù)內(nèi)存區(qū)域。比如設備驅(qū)動程序需要一個用于DMA的大緩沖區(qū)時,或者大頁內(nèi)存機制分頁時。內(nèi)存compact可以解決了內(nèi)存碎片的問題,這個機制將被占用的頁面,從內(nèi)存區(qū)域合適的移動,以換取大塊的空閑物理頁的過程,由kcompactd守護進程完成。
1.2.9 OOM killer
機器上的內(nèi)存可能會被耗盡,并且內(nèi)核將無法回收足夠的內(nèi)存用于運行新的程序,為了保存系統(tǒng)的其余部分,內(nèi)核會調(diào)用OOM killer殺掉一些進程,以釋放內(nèi)存。
1.3 段頁機制簡介
段頁機制是操作系統(tǒng)管理內(nèi)存的一種方式,簡單的來說,就是如何管理、組織系統(tǒng)中的內(nèi)存。要理解這種機制,需要了解一下內(nèi)存尋址的發(fā)展歷程。
- 直接尋址:早期的內(nèi)存很小,通過硬編碼的形式,直接定位到內(nèi)存地址。這種方式有著明顯的缺點:可控性弱、難以重定位、難以維護
- 分段機制:8086處理器,尋址空間達到1MB,即地址線擴展了20位,由于制作20位的寄存器較為困難,為了能在16位的寄存器的基礎(chǔ)上,尋址20位的地址空間,引入了段的概念,即內(nèi)存地址=段基址左移4位+偏移
- 分頁機制:隨著尋址空間的進一步擴大、虛擬內(nèi)存技術(shù)的引入,操作系統(tǒng)引入了分頁機制。引入分頁機制后,邏輯地址經(jīng)過段機制轉(zhuǎn)換得到的地址僅是中間地址,還需要通過頁機制轉(zhuǎn)換,才能得到實際的物理地址。邏輯地址 -->(分段機制) 線性地址 -->(分頁機制) 物理地址。
段頁機制詳見:https://blog.lecury.cn/2017/05/05/內(nèi)存尋址之段頁存儲機制分析/
2. 進階篇
2.1 內(nèi)存分配
2.1.1 大塊內(nèi)存的分配
掃盲篇也提到,Linux基于段頁式機制管理物理內(nèi)存,內(nèi)存被分割成一個個頁框,由多級頁表管理。除此之外,由于硬件的約束:
- DMA處理器,只能對RAM的前16MB尋址。
- 32位機器CPU最大尋址空間,只有4GB,對于大容量超過4GB的RAM,無法訪問所有的地址空間。
Linux還將物理內(nèi)存劃分為不同的管理區(qū):ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM,每個管理區(qū)都有自己的描述符,也有自己的頁框分配器,示意圖如下:
對于連續(xù)頁框組的內(nèi)存分配請求,是由管理區(qū)分配器完成,每個管理區(qū)的頁框分配是通過伙伴系統(tǒng)算法來實現(xiàn)。內(nèi)核經(jīng)常請求和釋放單個頁框,為了提高性能,每個內(nèi)存管理區(qū),還定義了一個CPU頁框高速緩存,包含一些預選分配的頁框。
伙伴系統(tǒng)算法:內(nèi)核為分配一組連續(xù)的頁框而建立的一種健壯、高效的分配策略,這種策略緩解了內(nèi)存碎片的發(fā)生。算法的核心思想:是把所有的空閑頁框分組為11個塊鏈表,每個塊鏈表分別包含1、2、4、8、16、...、512、1024個連續(xù)頁框。舉個簡單的例子,說明算法的工作過程。
假設需要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ǔ)上,進一步細分小內(nèi)存對象的分配。slab 緩存分配器提供了很多優(yōu)點,
- 首先,內(nèi)核通常依賴于對小對象的分配,它們會在系統(tǒng)生命周期內(nèi)進行無數(shù)次分配,slab 緩存分配器通過對類似大小的對象進行緩存,從而避免了常見的碎片問題。
- slab 分配器還支持通用對象的初始化,從而避免了為同一目而對一個對象重復進行初始化。
- 最后slab 分配器還可以支持硬件緩存對齊和著色,這允許不同緩存中的對象占用相同的緩存行,從而提高緩存的利用率并獲得更好的性能。
slab分配器詳見:http://www.secretmango.com/jimb/Whitepapers/slabs/slab.html
備注: slab著色主要是為了更好的利用CPU L1 cache,所使用的地址偏移策略。如果slab分配對象后還有空間剩余,就會把剩余的空間進行著色處理,盡可能將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 實存、虛存
實存:進程分配的、加載到主存中的內(nèi)存。包含來自共享庫的內(nèi)存,只要這些庫占用的頁框還在主存中,也包含所有正在使用的堆棧和堆內(nèi)存??梢酝ㄟ^ ps -o rss 查看進程的實存大小。
虛存:包含進程可以訪問的所有內(nèi)存,包含被換出、已經(jīng)分配但還未使用的內(nèi)存,以及來自共享庫的內(nèi)存??梢酝ㄟ^ ps -o vsz 查看進程的虛存大小。
舉個例子,如果進程A具有500K二進制文件并且鏈接到2500K共享庫,則具有200K的堆棧/堆分配,其中100K實際上在內(nèi)存中(其余是交換或未使用),并且它實際上只加載了1000K的共享庫然后是400K自己的二進制文件:
RSS: 400K + 1000K + 100K = 1500K VSZ: 500K + 2500K + 200K = 3200K實存和虛存是怎么轉(zhuǎn)換的呢?當程序嘗試訪問的地址未處于實存中時,就發(fā)生頁面錯誤,操作系統(tǒng)必須以某種方式處理這種錯誤,從而使應用程序正常運行。這些操作可以是:
- 找到頁面駐留在磁盤上的位置,并加載到主存中。
- 重新配置MMU,更新線性地址和物理地址的映射關(guān)系。
- 等。
隨著進程頁面錯誤的增長,主存中可用頁面越來越少,為了防止內(nèi)存完全耗盡,操作系統(tǒng)必須盡快釋放主存中暫時不用的頁面,以釋放空間供以后使用,方式如下:
- 將修改后的頁面寫入到磁盤的專用區(qū)域上(調(diào)頁空間或者交換區(qū))。
- 將未修改的頁面標記為空閑(沒必要寫入磁盤,因為沒有被修改)。
調(diào)頁或者交換是操作系統(tǒng)的正常部分,需要注意的是過度交換,這表示當前主存空間不足,頁面換出抖動對系統(tǒng)極為不利,會導致CPU和I/O負載升高,極端情況下,會造成操作系統(tǒng)所有的資源花費在調(diào)頁層面。
2.3 page cache
Linux中通過page cache機制來加速對磁盤文件的許多訪問,當它首次讀取或?qū)懭霐?shù)據(jù)介質(zhì)時,Linux會將數(shù)據(jù)存儲在未使用的內(nèi)存區(qū)中,通過這些區(qū)域充當緩存,如果再次讀取這些數(shù)據(jù)時,直接從內(nèi)存中快速獲取該數(shù)據(jù)。當發(fā)生寫操作時,Linux不會立刻執(zhí)行磁盤寫操作,而是把page cache中的頁面標記為臟頁,定期同步到存儲設備中。
可以通過free -m來查看page cache情況:
total used free shared buffers cached Mem: 32013 31288 724 0 241 12000 -/+ buffers/cache: 19046 12966 Swap: 32767 23134 9633cached這列顯示了page cache的情況。
總結(jié)
以上是生活随笔為你收集整理的linux内存管理_浅谈Linux内存管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python增加一列数据_Python编
- 下一篇: vs2010 编译linux,VS201