linux内存管理_浅谈Linux内存管理
1. 掃盲篇
1.1 操作系統存儲層次
常見的計算機存儲層次如下:
- 寄存器:CPU提供的,讀寫ns級別,容量字節級別。
- CPU緩存:CPU和CPU間的緩存,讀寫10ns級別,容量較大一些,百到千節。
- 主存:動態內存,讀寫100ns級別,容量GB級別。
- 外部存儲介質:磁盤、SSD,讀寫ms級別,容量可擴展到TB級別。
CPU內的緩存示意圖如下:
其中 L1d 和 L1i 都是CPU內部的cache,
- L1d 是數據cache。
- L1i 是指令緩存。
- L2是CPU內部的,不區分指令和數據的。
- 由于現代PC有多個CPU,L3緩存多個核心共用一個。
對于編程人員來說,絕大部分觀察主存和外部存儲介質就可以了。如果要做極致的性能優化,可以關注L1、L2、L3的cache,比如nginx的綁核操作、pthread調度會影響CPU cache等。
1.2 內存管理概述
MMU(內存管理單元):通過CPU將線性地址轉換成物理地址。
1.2.1 虛擬內存
物理內存是有限的(即使支持了熱插拔)、非連續的,不同的CPU架構對物理內存的組織都不同。這使得直接使用物理內存非常復雜,為了降低使用內存的復雜度,引入了虛擬內存機制。
虛擬內存抽象了應用程序物理內存的細節,只允許物理內存保存所需的信息(按需分頁),并提供了一種保護和控制進程間數據共享數據的機制。有了虛擬內存機制之后,每次訪問可以使用更易理解的虛擬地址,讓CPU轉換成實際的物理地址訪問內存,降低了直接使用、管理物理內存的門檻。
物理內存按大小被分成頁框、頁,每塊物理內存可以被映射為一個或多個虛擬內存頁。這塊映射關系,由操作系統的頁表來保存,頁表是有層級的。層級最低的頁表,保存實際頁面的物理地址,較高層級的頁表包含指向低層級頁表的物理地址,指向頂級的頁表的地址,駐留在寄存器中。當執行地址轉換時,先從寄存器獲取頂級頁表地址,然后依次索引,找到具體頁面的物理地址。
1.2.2 大頁機制
虛擬地址轉換的過程中,需要好幾個內存訪問,由于內存訪問相對CPU較慢,為了提高性能,CPU維護了一個TLB地址轉換的cache,TLB是比較重要且珍稀的緩存,對于大內存工作集的應用程序,會因TLB命中率低大大影響到性能。
為了減少TLB的壓力,增加TLB緩存的命中率,有些系統會把頁的大小設為MB或者GB,這樣頁的數目少了,需要轉換的頁表項也小了,足以把虛擬地址和物理地址的映射關系,全部保存于TLB中。
1.2.3 區域概念
通常硬件會對訪問不同的物理內存的范圍做出限制,在某些情況下設備無法對所有的內存區域做DMA。在其他情況下,物理內存的大小也會超過了虛擬內存的最大可尋址大小,需要執行特殊操作,才能訪問這些區域。這些情況下,Linux對內存頁的可能使用情況將其分組到各自的區域中(方便管理和限制)。比如ZONE_DMA用于指明哪些可以用于DMA的區域,ZONE_HIGHMEM包含未永久映射到內核地址空間的內存,ZONE_NORMAL標識正常的內存區域。
1.2.4 節點
多核CPU的系統中,通常是NUMA系統(非統一內存訪問系統)。在這種系統中,內存被安排成具有不同訪問延遲的存儲組,這取決于與處理器的距離。每一個庫,被稱為一個節點,每個節點Linux構建了一個獨立的內存管理子系統。一個節點有自己的區域集、可用頁和已用頁表和各種統計計數器。
1.2.5 page cache
從外部存儲介質中加載數據到內存中,這個過程是比較耗時的,因為外部存儲介質讀寫性能毫秒級。為了減少外部存儲設備的讀寫,Linux內核提供了Page cache。最常見的操作,每次讀取文件時,數據都會被放入頁面緩存中,以避免后續讀取時所進行昂貴的磁盤訪問。同樣,當寫入文件時,數據被重新放置在緩存中,被標記為臟頁,定期的更新到存儲設備上,以提高讀寫性能。
1.2.6 匿名內存
匿名內存或者匿名映射表示不受文件系統支持的內存,比如程序的堆棧隱式創立的,或者顯示通過mmap創立的。
1.2.7 內存回收
貫穿系統的生命周期,一個物理頁可存儲不同類型的數據,可以是內核的數據結構,或是DMA訪問的buffer,或是從文件系統讀取的數據,或是用戶程序分配的內存等。
根據頁面的使用情況,Linux內存管理對其進行了不同的處理,可以隨時釋放的頁面,稱之為可回收頁面,這類頁面為:頁面緩存或者是匿名內存(被再次交換到硬盤上)
大多數情況下,保存內部內核數據并用DMA緩沖區的頁面是不能重新被回收的,但是某些情況下,可以回收使用內核數據結構的頁面。例如:文件系統元數據的內存緩存,當系統處于內存壓力情況下,可以從主存中丟棄它們。
釋放可回收的物理內存頁的過程,被稱之為回收,可以同步或者異步的回收操作。當系統負載增加到一定程序時,kswapd守護進程會異步的掃描物理頁,可回收的物理頁被釋放,并逐出備份到存儲設備。
1.2.8 compaction
系統運行一段時間,內存就會變得支離破碎。雖然使用虛擬村內可以將分散的物理頁顯示為連續的物理頁,但有時需要分配較大的物理連續內存區域。比如設備驅動程序需要一個用于DMA的大緩沖區時,或者大頁內存機制分頁時。內存compact可以解決了內存碎片的問題,這個機制將被占用的頁面,從內存區域合適的移動,以換取大塊的空閑物理頁的過程,由kcompactd守護進程完成。
1.2.9 OOM killer
機器上的內存可能會被耗盡,并且內核將無法回收足夠的內存用于運行新的程序,為了保存系統的其余部分,內核會調用OOM killer殺掉一些進程,以釋放內存。
1.3 段頁機制簡介
段頁機制是操作系統管理內存的一種方式,簡單的來說,就是如何管理、組織系統中的內存。要理解這種機制,需要了解一下內存尋址的發展歷程。
- 直接尋址:早期的內存很小,通過硬編碼的形式,直接定位到內存地址。這種方式有著明顯的缺點:可控性弱、難以重定位、難以維護
- 分段機制:8086處理器,尋址空間達到1MB,即地址線擴展了20位,由于制作20位的寄存器較為困難,為了能在16位的寄存器的基礎上,尋址20位的地址空間,引入了段的概念,即內存地址=段基址左移4位+偏移
- 分頁機制:隨著尋址空間的進一步擴大、虛擬內存技術的引入,操作系統引入了分頁機制。引入分頁機制后,邏輯地址經過段機制轉換得到的地址僅是中間地址,還需要通過頁機制轉換,才能得到實際的物理地址。邏輯地址 -->(分段機制) 線性地址 -->(分頁機制) 物理地址。
段頁機制詳見:https://blog.lecury.cn/2017/05/05/內存尋址之段頁存儲機制分析/
2. 進階篇
2.1 內存分配
2.1.1 大塊內存的分配
掃盲篇也提到,Linux基于段頁式機制管理物理內存,內存被分割成一個個頁框,由多級頁表管理。除此之外,由于硬件的約束:
- DMA處理器,只能對RAM的前16MB尋址。
- 32位機器CPU最大尋址空間,只有4GB,對于大容量超過4GB的RAM,無法訪問所有的地址空間。
Linux還將物理內存劃分為不同的管理區:ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM,每個管理區都有自己的描述符,也有自己的頁框分配器,示意圖如下:
對于連續頁框組的內存分配請求,是由管理區分配器完成,每個管理區的頁框分配是通過伙伴系統算法來實現。內核經常請求和釋放單個頁框,為了提高性能,每個內存管理區,還定義了一個CPU頁框高速緩存,包含一些預選分配的頁框。
伙伴系統算法:內核為分配一組連續的頁框而建立的一種健壯、高效的分配策略,這種策略緩解了內存碎片的發生。算法的核心思想:是把所有的空閑頁框分組為11個塊鏈表,每個塊鏈表分別包含1、2、4、8、16、...、512、1024個連續頁框。舉個簡單的例子,說明算法的工作過程。
假設需要256個頁框的連續內存,算法先在256個頁框的鏈表中,檢查是否還有空閑塊,如果有就分配出去。如果沒有,算法會找到下一個更大的512頁框的鏈表,如果存在空閑塊,內核會把512頁框分割成兩部分,一半用來分配,另一半插入到256頁框的鏈表中。
2.1.2 小塊內存的分配
伙伴系統算法采用頁框作為基本的內存區,這適合于大塊內存的請求。對于小塊內存的分配,是采用的slab分配器算法來實現的。slab并沒有脫離伙伴系統算法,而是基于伙伴系統分配的大內存基礎上,進一步細分小內存對象的分配。slab 緩存分配器提供了很多優點,
- 首先,內核通常依賴于對小對象的分配,它們會在系統生命周期內進行無數次分配,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 非連續內存的分配
把內存區映射到一組連續的頁框是最好的選擇,這樣會充分利用高速緩存。如果對內存區的請求不是很頻繁,那么分配非連續的頁框,會是比較好的選擇,因為這樣會避免外部碎片,缺點是內核的頁表比較亂。Linux以下方面使用了非連續內存區:
- 為活動交換區分配數據結構。
- 給某些I/O驅動程序分配緩沖區。
- 等
2.2 實存、虛存
實存:進程分配的、加載到主存中的內存。包含來自共享庫的內存,只要這些庫占用的頁框還在主存中,也包含所有正在使用的堆棧和堆內存。可以通過 ps -o rss 查看進程的實存大小。
虛存:包含進程可以訪問的所有內存,包含被換出、已經分配但還未使用的內存,以及來自共享庫的內存。可以通過 ps -o vsz 查看進程的虛存大小。
舉個例子,如果進程A具有500K二進制文件并且鏈接到2500K共享庫,則具有200K的堆棧/堆分配,其中100K實際上在內存中(其余是交換或未使用),并且它實際上只加載了1000K的共享庫然后是400K自己的二進制文件:
RSS: 400K + 1000K + 100K = 1500K VSZ: 500K + 2500K + 200K = 3200K實存和虛存是怎么轉換的呢?當程序嘗試訪問的地址未處于實存中時,就發生頁面錯誤,操作系統必須以某種方式處理這種錯誤,從而使應用程序正常運行。這些操作可以是:
- 找到頁面駐留在磁盤上的位置,并加載到主存中。
- 重新配置MMU,更新線性地址和物理地址的映射關系。
- 等。
隨著進程頁面錯誤的增長,主存中可用頁面越來越少,為了防止內存完全耗盡,操作系統必須盡快釋放主存中暫時不用的頁面,以釋放空間供以后使用,方式如下:
- 將修改后的頁面寫入到磁盤的專用區域上(調頁空間或者交換區)。
- 將未修改的頁面標記為空閑(沒必要寫入磁盤,因為沒有被修改)。
調頁或者交換是操作系統的正常部分,需要注意的是過度交換,這表示當前主存空間不足,頁面換出抖動對系統極為不利,會導致CPU和I/O負載升高,極端情況下,會造成操作系統所有的資源花費在調頁層面。
2.3 page cache
Linux中通過page cache機制來加速對磁盤文件的許多訪問,當它首次讀取或寫入數據介質時,Linux會將數據存儲在未使用的內存區中,通過這些區域充當緩存,如果再次讀取這些數據時,直接從內存中快速獲取該數據。當發生寫操作時,Linux不會立刻執行磁盤寫操作,而是把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的情況。
總結
以上是生活随笔為你收集整理的linux内存管理_浅谈Linux内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python增加一列数据_Python编
- 下一篇: vs2010 编译linux,VS201