㈠ 圖解|什麼是缺頁錯誤Page Fault
以32位的linux系統為例,每個進程獨立擁有4GB的虛擬地址空間,根據局部性原理沒有必要也不可能為每個進程分配4GB的物理地址空間。
64位系統也是一樣的道理,只不過空間定址范圍大了很多很多倍,進程的虛擬地址空間會分為幾個部分:
實際上只有程序運行時用到了才去內存中尋找虛擬地址對應的頁幀,找不到才可能進行分配,這就是內存的惰性(延時)分配機制。
對於一個運行中的進程來說,不是所有的虛擬地址在物理內存中都有對應的頁,如圖展示了部分虛擬地址存在對應物理頁的情況:
虛擬地址空間根據固定大小一般是4KB進行劃分,物理內存可以設置不同的頁面大小,通常物理頁大小和虛擬頁大小是一樣的,本文按照物理頁4KB大小展開。
經過前面的分析,我們將面臨一個問題: 如何將虛擬地址准確快速地映射到物理頁呢?
CPU並不直接和物理內存打交道,而是把地址轉換的活外包給了MMU,MMU是一種硬體電路,其速度很快,主要工作是進行內存管理,地址轉換只是它承接的業務之一。
一起看看MMU是如何搞定地址轉換的。
每個進程都會有自己的頁表Page Table,頁表存儲了進程中虛擬地址到物理地址的映射關系,所以就相當於一張地圖,MMU收到CPU的虛擬地址之後開始查詢頁表,確定是否存在映射以及讀寫許可權是否正常,如圖:
對於4GB的虛擬地址且大小為4KB頁,一級頁表將有2^20個表項,頁表佔有連續內存並且存儲空間大,多級頁表可以有效降低頁表的存儲空間以及內存連續性要求,但是多級頁表同時也帶來了查詢效率問題。
我們以2級頁表為例,MMU要先進行兩次頁表查詢確定物理地址,在確認了許可權等問題後,MMU再將這個物理地址發送到匯流排,內存收到之後開始讀取對應地址的數據並返回。
MMU在2級頁表的情況下進行了2次檢索和1次讀寫,那麼當頁表變為N級時,就變成了N次檢索+1次讀寫。
可見,頁表級數越多查詢的步驟越多,對於CPU來說等待時間越長,效率越低,這個問題還需要優化才行。
MMU和TLB的故事就這樣開始了...
CPU覺得MMU幹活雖然賣力氣,但是效率有點低,不太想繼續外包給它了,這一下子把MMU急壞了。
MMU於是找來了一些精通統計的朋友,經過一番研究之後發現CPU用的數據經常是一小搓,但是每次MMU都還要重復之前的步驟來檢索,害,就知道埋頭幹活了,也得講究方式方法呀!
找到瓶頸之後,MMU引入了新武器,江湖人稱快表的TLB,別看TLB容量小,但是正式上崗之後幹活還真是不含糊。
當CPU給MMU傳新虛擬地址之後,MMU先去問TLB那邊有沒有,如果有就直接拿到物理地址發到匯流排給內存,齊活。
TLB容量比較小,難免發生Cache Miss,這時候MMU還有保底的老武器頁表 Page Table,在頁表中找到之後MMU除了把地址發到匯流排傳給內存,還把這條映射關系給到TLB,讓它記錄一下刷新緩存。
TLB容量不滿的時候就直接把新記錄存儲了,當滿了的時候就開啟了淘汰大法把舊記錄清除掉,來保存新記錄,彷佛完美解決了問題。
在TLB和Page Table加持之下,CPU感覺最近MMU比較給力了,就問MMU怎麼做到的?MMU就一五一十告訴了CPU。
CPU說是個不錯的路子,隨後說出了自己的建議:TLB還是有點小,緩存不命中也是經常發生的,要不要搞個大的,這樣存儲更多訪問更快?
MMU一臉苦笑說道大哥TLB很貴的,要不你給漲點外包費?話音未落,CPU就說漲工資是不可能了,這輩子都不可能了。
設想 CPU給MMU的虛擬地址在TLB和Page Table都沒有找到對應的物理頁幀或者許可權不對,該怎麼辦呢?
沒錯,這就是缺頁異常Page Fault, 它是一個由硬體中斷觸發的可以由軟體邏輯糾正的錯誤 。
假如目標內存頁在物理內存中 沒有對應的頁幀或者存在但無對應許可權 , CPU 就無法獲取數據 ,這種情況下 CPU就會報告一個缺頁錯誤 。
由於CPU沒有數據就無法進行計算,CPU罷工了 用戶進程也就出現了缺頁中斷 ,進程會從用戶態切換到內核態,並將缺頁中斷交給內核的 Page Fault Handler 處理。
缺頁異常並不可怕,只要CPU要的虛擬地址經過MMU的一番定址之後沒有找到或者找到後無許可權,就會出現缺頁異常,因此觸發異常後的處理流程將是重點內容。
缺頁中斷會交給PageFaultHandler處理,其根據缺頁中斷的不同類型會進行不同的處理:
不同類型的Page Fault出現的原因也不一樣,常見的幾種原因包括:
本文粗淺地和大家一起學習了Page Fault的相關知識點,包括Linux虛擬地址和物理地址的關系、CPU獲取內存數據的過程、MMU和TLB&頁表的協同配合、缺頁異常產生的原因和分類處理。
本文並沒有對MMU的內部機制、內核態&用戶態缺頁異常、缺頁異常處理函數等內容進行展開,主要是因為這部分內容相對晦澀,還得靠自己深入研究。
㈡ Linux內存系統
維基網路——虛擬內存定義
All about Linux swap space
Linux將物理RAM (Random Access Memory) 劃分為稱為頁面的內存塊。交換是將一頁內存復制到硬碟上的預配置空間(稱為交換空間)以釋放改內存頁面上的過程。物理內存和交換空間的組合就是可用的虛擬內存量。
虛擬內存的那點事兒
進程是與其他進程共享CPU和內存資源的。為了有效的管理內存並減少出錯,現代操作系統提供了一種對主存的抽象概念,即:虛擬內存( Virtual Memory )。 虛擬內存為每個進程提供一個一致的,私有的地址空間,每個進程擁有一片連續完整的內存空間。
正如 維基網路 所說,虛擬內存不只是「使用硬碟空間來擴展內存」的技術。 虛擬內存的重要意義是它定義了一個連續的虛擬地址空間, 使得程序編寫難度降低。並且, 把內存擴展到硬碟空間只是使用虛擬內存的必然結果,虛擬內存空間會存在硬碟中,並且會被全部放入內存中緩沖(按需),有的操作系統還會在內存不夠的情況下,將一進程的內存全部放入硬碟空間中,並在切換到進程時再從硬碟讀取 (這也是Windows會經常假死的原因...)。
虛擬內存主要提供了如下三個重要的能力:
內存通常被組織為一個由M個連續的位元組大小的單元組成的數組。每個位元組都有一個唯一的物理地址 (Physical Address PA) ,作為到數組的索引。
CPU訪問內存最簡單直接的方法就是使用物理地址,這種定址方式稱為 物理定址 。
現代計算機使用的是一種被稱為虛擬定址 (Virtual Addressing) 的定址方式。 使用虛擬定址,CPU需要將虛擬地址翻譯成物理地址,這樣才能訪問到真實的物理內存。
虛擬定址需要硬體與操作系統之間相互合作。 CPU中含有一個被稱為內存管理單元 (Memory Management Unit,MMU) 的硬體,它的功能是將虛擬地址轉換稱為物理地址,MMU需要藉助存放在內存中的 頁表 來動態翻譯虛擬地址,該頁表由操作系統管理。
分頁表是一種數據結構,它用於計算機操作系統中虛擬內存系統,其存儲了虛擬地址到物理地址之間的映射。虛擬地址在訪問進程中是唯一的,而物理地址在硬體(比如內存)中是唯一的。
在操作系統中使用 虛擬內存 ,每個進程會認為使用一塊大的連續的內存,事實上,每個進程的內存散布在 物理內存 的不同區域。或者可能被調出到備份存儲中(一般是硬碟)。當一個進程請求自己的內存,操作系統負責把程序生成的虛擬地址,映射到實際存儲的物理內存上。操作系統在 分頁表 中存儲虛擬地址到物理地址的映射。每個映射被稱為 分頁表項(page table entry ,PTE) 。
在一個簡單的地址空間方案中,由虛擬地址定址的頁與物理內存中的幀之間的關系。物理內存可以包含屬於許多進程的頁。如果不經常使用,或者物理內存已滿,可以將頁面分頁到磁碟。在上圖中,並非所有頁面都在物理內存中。
虛擬地址到物理地址的轉換(即虛擬內存的管理)、內存保護、CPU高速緩存的控制。
現代的內存管理單元是以 頁 的方式,分割虛擬地址空間(處理器使用的地址范圍)的;頁的大小是2的n次方,通常為幾KB(位元組)。地址尾部的n位(頁大小的2的次方數)作為頁內的偏移量保持不變。其餘的地址位(address)為(虛擬)頁號。
內存管理單元通常藉助一種叫做轉譯旁觀緩沖器(Translation Lookaside Buffer,TLB)和相聯高速緩存來將虛擬頁號轉換為物理頁號。當後備緩沖器中沒有轉換記錄時,則使用一種較慢的機制,其中包括專用硬體的數據結構或軟體輔助手段。這個數據結構稱為 分頁表 ,頁表中的數據叫做 分頁表項 (page table entry PTE)。物理頁號結合頁偏移量便提供了完整的物理地址。
頁表 或 轉換後備緩沖器數據項應該包括的信息有:
有時候,TLB和PTE會 禁止對虛擬頁訪問 ,這可能是因為沒有RAM與虛擬頁相關聯。如果是這種情況,MMU將向CPU發出頁錯誤的信號,操作系統將進行處理,也許會尋找RAM的空白幀,同時建立一個新的PTE將之映射到所請求的虛擬地址。如果沒有空閑的RAM,可能必須關閉一個已經存在的頁面,使用一些替換演算法,將之保存到磁碟中(這被稱為頁面調度)。
當需要將虛擬地址轉換為物理地址時,首先搜索TLB,如果找到匹配(TLB)命中,則返回物理地址並繼續存儲器訪問。然而,如果沒有匹配(稱為TLB未命中),則MMU或操作系統TLB未命中處理器通常會查找 頁表 中的地址映射以查看是否存在映射(頁面遍歷),如果存在,則將其寫回TLB(這必須完成,因為硬體通過虛擬存儲器系統中的TLB訪問存儲器),並且重啟錯誤指令(這也可以並行發生)。此後續轉換找到TLB命中,並且內存訪問將繼續。
虛擬地址到物理地址的轉換過程,如果虛擬內存不存在與TLB,轉換會被重置並通過分頁表和硬體尋找。
通常情況下,用於處理此中斷的程序是操作系統的一部分。如果操作系統判斷此次訪問有效,那麼 操作系統會嘗試將相關的分頁從硬碟上的虛擬內存文件調入內存。 而如果訪問是不被允許的,那麼操作系統通常會結束相關的進程。
雖然叫做「頁缺失」錯誤,但實際上這並不一定是一種錯誤。而且這一機制是利用虛擬內存來增加程序可用內存空間。
發生這種情況的可能性:
當原程序再次需要該頁內的數據時,如果這一頁確實沒有被分配出去,那麼系統只需要重新為該頁在MMU內注冊映射即可。
操作系統需要:
硬性頁缺失導致的性能損失是很大的。
另外,有些操作系統會將程序的一部分延遲到需要使用的時候再載入入內存執行,以此提升性能。這一特性也是通過捕獲硬性頁缺失達到的。
當硬性頁缺失過於頻繁發生時,稱發生 系統顛簸。
具體動作與所使用的操作系統有關,比如Windows會使用異常機制向程序報告,而類Unix系統則使用信號機制。
盡管在整個運行過程中,程序引用不同的頁面總數(也就是虛擬內存大小)可能超出了物理存儲器(DRAM)總大小,但是程序常常在較小的活動頁面上活動,這個集合叫做工作集或者常駐集。在工作集被緩存後,對它的反復調用會使程序命中提高,從而提高性能。
大部分的程序都可以在存儲器獲取數據和讀取中達到穩定的狀態,當程序達到穩定狀態時,存儲器的使用量通常都不會太大。虛擬內存雖然可以有效率控制存儲器的使用, 但是大量的頁缺失還是造成了系統遲緩的主要因素。 當工作集的大小超過物理存儲器大小,程序將會發生一種不幸的情況,這種情況稱為 「顛簸」 ,頁面將不停的寫入、釋放、讀取,由於大量的丟失(而非命中)而損失極大性能。用戶可以增加隨機存取存儲器的大小或是減少同時在系統里運行程序的數量來降低系統顛簸的記錄。
推薦閱讀:
操作系統--分頁(一)
操作系統實現(二):分頁和物理內存管理
㈢ linux 編譯常量被重復定義了怎麼辦
宏phys定義了你的機器上的地址轉換__virt_to_phys()。這個宏用於把虛擬地址轉換為一個物理地址。通常情況下:
phys = virt - PAGE_OFFSET PHYS_OFFSET 解壓縮器的地址地址。由於當你調用解壓縮器代碼時,通常關閉MMU,因此這里並不討論虛擬地址和物理地址的問題。通常你在這個地址處調用內核,開始引導內核。它不需要在RAM中,只需要位於FLASH或其他只讀或讀/寫的可定址的存儲設備中。
l ZBSSADDR
解壓縮器的初始化為0的工作區的起始地址。必須位於RAM中,解壓縮器會替你把它初始化為0,此外,需要關閉MMU。
l ZRELADDR
解壓縮內核將被寫入的地址和最終的執行地址。必須滿足:
__virt_to_phys(TEXTADDR) == ZRELADDR
內核的開始部分被編碼為與位置無關的代碼。
l INITRD_PHYS
放置初始RAM盤的物理地址。僅當你使用bootpImage時相關(這是一種非常老的param_struct結構)
l INITRD_ⅥRT
初始RAM盤的虛擬地址。必須滿足:
__virt_to_phys(INITRD_ⅥRT) == INITRD_PHYS
l PARAMS_PHYS
param_struct 結構體或tag lis的物理地址,用於給定內核執行環境下的不同參數。 RAM第一個BANK的物理地址地址。
l PAGE_OFFSET
RAM第一個BANK的虛擬地址地址。在內核引導階段,虛擬地址PAGE_OFFSE將被映射為物理地址PHYS_OFFSET,它應該與TASK_SIZE具有相同的值。
l TASK_SIZE
一個用戶進程的最大值,單位為byte。用戶空間的堆棧從這個地址處向下增長。
任何一個低於TASK_SIZE的虛擬地址對用戶進程來說都是不可見的,因此,內核通過進程偏移對每個進行進行動態的管理。我把這叫做用戶段。任何高於TASK_SIZE的對所有進程都是相同的,稱之為內核段。(換句話說,你不能把IO映射放在低於TASK_SIZE和PAGE_OFFSET的位置處。)
l TEXTADDR
內核的虛擬起始地址,通常為PAGE_OFFSET 0x8000。內核映射必須在此結束。
l DATAADDR
內核數據段的虛擬地址,不能在使用解壓縮器的情況下定義。
l VMALLOC_START
l VMALLOC_END
用於限制vmalloc()區域的虛擬地址。此地址必須位於內核段。通常,vmalloc()區域在最後的虛擬RAM地址以上開始VMALLOC_OFFSET位元組。
l VMALLOC_OFFSET
Offset normally incorporated into VMALLOC_START to provide a hole between virtual RAM and the vmalloc area. We do this to allow out of bounds memory accesses (eg,something writing off the end of the mapped memory map) to be caught. Normally set to 8MB. pram——指定了RAM起始的物理地址,必須始終存在,並應等於PHYS_OFFSET。
pio——是供arch/arm/kernel/debug-armv.S中的調試宏使用的,包含IO的8 MB區域的物理地址。
vio——是8MB調試區域的虛擬地址。
這個調試區域將被位於代碼中(通過MAPIO函數)的隨後的構架相關代碼再次進行初始化。
l BOOT_PARAMS
參見 PARAMS_PHYS.
l FⅨUP(func)
機器相關的修正,在存儲子系統被初始化前運行。
l MAPIO(func)
機器相關的函數,用於IO區域的映射(包括上面的調試區)。
l INITIRQ(func)
用於初始化中斷的機器相關的函數 。
㈣ linux kernel 內存管理-頁表、TLB
頁表用來把虛擬頁映射到物理頁,並且存放頁的保護位(即訪問許可權)。
在Linux4.11版本以前,Linux內核把頁表分為4級:
頁全局目錄表(PGD)、頁上層目錄(PUD)、頁中間目錄(PMD)、直接頁表(PT) 。
4.11版本把頁表擴展到5級,在頁全局目錄和頁上層目錄之間增加了 頁四級目錄(P4D) 。
各處處理器架構可以選擇使用5級,4級,3級或者2級頁表,同一種處理器在頁長度不同的情況可能選擇不同的頁表級數。可以使用配置宏CONFIG_PGTABLE_LEVELS配置頁表的級數,一般使用默認值。
如果選擇4級頁表,那麼使用PGD,PUD,PMD,PT;如果使用3級頁表,那麼使用PGD,PMD,PT;如果選擇2級頁表,那麼使用PGD和PT。 如果不使用頁中間目錄 ,那麼內核模擬頁中間目錄,調用函數pmd_offset 根據頁上層目錄表項和虛擬地址獲取頁中間目錄表項時 , 直接把頁上層目錄表項指針強制轉換成頁中間目錄表項 。
每個進程有獨立的頁表,進程的mm_struct實例的成員pgd指向頁全局目錄,前面四級頁表的表項存放下一級頁表的起始地址,直接頁表的頁表項存放頁幀號(PFN) 。
內核也有一個頁表, 0號內核線程的進程描述符init_task的成員active_mm指向內存描述符init_mm,內存描述符init_mm的成員pgd指向內核的頁全局目錄swapper_pg_dir 。
ARM64處理器把頁表稱為轉換表,最多4級。ARM64處理器支持三種頁長度:4KB,16KB,64KB。頁長度和虛擬地址的寬度決定了轉換表的級數,在虛擬地址的寬度為48位的條件下,頁長度和轉換表級數的關系如下所示:
ARM64處理器把表項稱為描述符,使用64位的長描述符格式。描述符的0bit指示描述符是不是有效的:0表示無效,1表示有效。第1位指定描述符類型。
在塊描述符和頁描述符中,內存屬性被拆分為一個高屬性和一個低屬性塊。
處理器的MMU負責把虛擬地址轉換成物理地址,為了改進虛擬地址到物理地址的轉換速度,避免每次轉換都需要查詢內存中的頁表,處理器廠商在管理單元里加了稱為TLB的高速緩存,TLB直譯為轉換後備緩沖區,意譯為頁表緩存。
頁表緩存用來緩存最近使用過的頁表項, 有些處理器使用兩級頁表緩存 : 第一級TLB分為指令TLB和數據TLB,好處是取指令和取數據可以並行;第二級TLB是統一TLB,即指令和數據共用的TLB 。
不同處理器架構的TLB表項的格式不同。ARM64處理器的每條TLB表項不僅包含虛擬地址和物理地址,也包含屬性:內存類型、緩存策略、訪問許可權、地址空間標識符(ASID)和虛擬機標識符(VMID)。 地址空間標識符區分不同進程的頁表項 , 虛擬機標識符區分不同虛擬機的頁表項 。
如果內核修改了可能緩存在TLB裡面的頁表項,那麼內核必須負責使舊的TLB表項失效,內核定義了每種處理器架構必須實現的函數。
當TLB沒有命中的時候,ARM64處理器的MMU自動遍歷內存中的頁表,把頁表項復制到TLB,不需要軟體把頁表項寫到TLB,所以ARM64架構沒有提供寫TLB的指令。
為了減少在進程切換時清空頁表緩存的需要,ARM64處理器的頁表緩存使用非全局位區分內核和進程的頁表項(nG位為0表示內核的頁表項), 使用地址空間標識符(ASID)區分不同進程的頁表項 。
ARM64處理器的ASID長度是由具體實現定義的,可以選擇8位或者16位。寄存器TTBR0_EL1或者TTBR1_EL1都可以用來存放當前進程的ASID,通常使用寄存器TCR_EL1的A1位決定使用哪個寄存器存放當前進程的ASID,通常使用寄存器 TTBR0_EL1 。寄存器TTBR0_EL1的位[63:48]或者[63:56]存放當前進程的ASID,位[47:1]存放當前進程的頁全局目錄的物理地址。
在SMP系統中,ARM64架構要求ASID在處理器的所有核是唯一的。假設ASID為8位,ASID只有256個值,其中0是保留值,可分配的ASID范圍1~255,進程的數量可能超過255,兩個進程的ASID可能相同,內核引入ASID版本號解決這個問題。
(1)每個進程有一個64位的軟體ASID, 低8位存放硬體ASID,高56位存放ASID版本號 。
(2) 64位全局變數asid_generation的高56位保存全局ASID版本號 。
(3) 當進程被調度時,比較進程的ASID版本號和全局版本號 。如果版本號相同,那麼直接使用上次分配的ASID,否則需要給進程重新分配硬體ASID。
存在空閑ASID,那麼選擇一個分配給進程。不存在空閑ASID時,把全局ASID版本號加1,重新從1開始分配硬體ASID,即硬體ASID從255回繞到1。因為剛分配的硬體ASID可能和某個進程的ASID相同,只是ASID版本號不同,頁表緩存可能包含了這個進程的頁表項,所以必須把所有處理器的頁表緩存清空。
引入ASID版本號的好處是:避免每次進程切換都需要清空頁表緩存,只需要在硬體ASID回環時把處理器的頁表緩存清空 。
虛擬機裡面運行的客戶操作系統的虛擬地址轉物理地址分兩個階段:
(1) 把虛擬地址轉換成中間物理地址,由客戶操作系統的內核控制 ,和非虛擬化的轉換過程相同。
(2) 把中間物理地址轉換成物理地址,由虛擬機監控器控制 ,虛擬機監控器為每個虛擬機維護一個轉換表,分配一個虛擬機標識符,寄存器 VTTBR_EL2 存放當前虛擬機的階段2轉換表的物理地址。
每個虛擬機有獨立的ASID空間 ,頁表緩存使用 虛擬機標識符 區分不同虛擬機的轉換表項,避免每次虛擬機切換都要清空頁表緩存,在虛擬機標識符回繞時把處理器的頁表緩存清空。
㈤ Linux 內核的內存管理 - 概念
Concepts overview — The Linux Kernel documentation
Linux中的內存管理是一個復雜的系統,經過多年的發展,它包含越來越多的功能,以支持從 MMU-less microcontrollers 到 supercomputers 的各種系統。
沒有MMU內存管理的系統被稱為 nommu ,它值得寫一份專門的文檔進行描述。
盡管有些概念是相同的,這里我們假設MMU可用,CPU可以將虛擬地址轉換為物理地址。
計算機系統中的物理內存是有限資源,即便支持內存熱插拔,其可以安裝的內存也有限的。物理內存不一定必須是連續的;它可以作為一組不同的地址范圍被訪問。此外,不同的CPU架構,甚至同架構的不同實現對如何定義這些地址范圍都是不同的。
這使得直接處理物理內存異常復雜,為了避免這種復雜性,開發了 虛擬內存 (virtual memory) 的概念。
虛擬內存從應用軟體中抽象出物理內存的細節,只允許在物理內存中保留需要的信息 (demand paging) ,並提供一種機制來保護和控制進程之間的數據共享。
通過虛擬內存,每次內存訪問都訪問一個 虛擬地址 。當CPU對從系統內存讀取(或寫入)的指令進行解碼時,它將該指令中編碼的虛擬地址轉換為內存控制器可以理解的物理地址。
物理內存被切分為 頁幀 page frames 或 頁 pages 。頁的大小是基於架構的。一些架構允許從幾個支持的值中選擇頁大小;此選擇在內核編譯時設置到內核配置。
每個物理內存頁都可以映射為一個或多個 虛擬頁(virtual pages) 。映射關系描述在 頁表(page tables) 中,頁表將程序使用的虛擬地址轉換為物理內存地址。頁表以層次結構組織。
最底層的表包含軟體使用的實際內存頁的物理地址。較高層的表包含較低層表頁的物理地址。頂層表的指針駐留在寄存器中。
當CPU進行地址轉換的時候,它使用寄存器訪問頂級頁表。
虛擬地址的高位,用於頂級頁表的條目索引。然後,通過該條目訪問下級,下級的虛擬地址位又作為其下下級頁表的索引。虛擬地址的最低位定義實際頁內的偏移量。
地址轉換需要多次內存訪問,而內存訪問相對於CPU速度來說比較慢。為了避免在地址轉換上花費寶貴的處理器周期,CPU維護著一個稱為 TLB (Translation Lookaside Buffer)的用於地址轉換緩存(cache)。通常TLB是非常稀缺的資源,需要大內存工作應用程序會因為TLB未命中而影響性能。
很多現代CPU架構允許頁表的高層直接映射到內存頁。例如,x86架構,可以通過二級、三級頁表的條目映射2M甚至1G內存頁。在Linux中,這些內存頁稱為 大頁 (Huge) 。大頁的使用顯著降低了TLB的壓力,提高了TLB命中率,從而提高了系統的整體性能。
Linux提供兩種機制開啟使用大頁映射物理內存。
第一個是 HugeTLB 文件系統,即 hugetlbfs 。它是一個偽文件系統,使用RAM作為其存儲。在此文件系統中創建的文件,數據駐留在內存中,並使用大頁進行映射。
關於 HugeTLB Pages
另一個被稱為 THP (Transparent HugePages) ,後出的開啟大頁映射物理內存的機制。
與 hugetlbfs 不同,hugetlbfs要求用戶和/或系統管理員配置系統內存的哪些部分應該並可以被大頁映射;THP透明地管理這些映射並獲取名稱。
關於 Transparent Hugepage Support
通常,硬體對不同物理內存范圍的訪問方式有所限制。某些情況下,設備不能對所有可定址內存執行DMA。在其他情況下,物理內存的大小超過虛擬內存的最大可定址大小,需要採取特殊措施來訪問部分內存。還有些情況,物理內存的尺寸超過了虛擬內存的最大可定址尺寸,需要採取特殊措施來訪問部分內存。
Linux根據內存頁的使用情況,將其組合為多個 zones 。比如, ZONE_DMA 包含設備用於DMA的內存, ZONE_HIGHMEM 包含未永久映射到內核地址空間的內存, ZONE_NORMAL 包含正常定址內存頁。
內存zones的實際層次架構取決於硬體,因為並非所有架構都定義了所有的zones,不同平台對DMA的要求也不同。
多處理器機器很多基於 NUMA (Non-Uniform Memory Access system - 非統一內存訪問系統 )架構。 在這樣的系統中,根據與處理器的「距離」,內存被安排成具有不同訪問延遲的 banks 。每個 bank 被稱為一個 node ,Linux為每個 node 構造一個獨立的內存管理子系統。 Node 有自己的zones集合、free&used頁面列表,以及各種統計計數器。
What is NUMA?
NUMA Memory Policy
物理內存易失,將數據放入內存的常見情況是讀取文件。讀取文件時,數據會放入 頁面緩存(page cache) ,可以在再次讀取時避免耗時的磁碟訪問。同樣,寫文件時,數據也會被放入 頁面緩存 ,並最終進入存儲設備。被寫入的頁被標記為 臟頁(dirty page) ,當Linux決定將其重用時,它會將更新的數據同步到設備上的文件。
匿名內存 anonymous memory 或 匿名映射 anonymous mappings 表示沒有後置文件系統的內存。這些映射是為程序的stack和heap隱式創建的,或調用mmap(2)顯式創建的。通常,匿名映射只定義允許程序訪問的虛擬內存區域。讀,會創建一個頁表條目,該條目引用一個填充有零的特殊物理頁。寫,則分配一個常規物理頁來保存寫入數據。該頁將被標記為臟頁,如果內核決定重用該頁,則臟頁將被交換出去 swapped out 。
縱貫整個系統生命周期,物理頁可用於存儲不同類型的數據。它可以是內核內部數據結構、設備驅動DMA緩沖區、讀取自文件系統的數據、用戶空間進程分配的內存等。
根據內存頁使用情況,Linux內存管理會區別處理。可以隨時釋放的頁面稱為 可回收(reclaimable) 頁面,因為它們把數據緩存到了其他地方(比如,硬碟),或者被swap out到硬碟上。
可回收頁最值得注意的是 頁面緩存 和 匿名頁面 。
在大多數情況下,存放內部內核數據的頁,和用作DMA緩沖區的頁無法重用,它們將保持現狀直到用戶釋放。這樣的被稱為 不可回收頁(unreclaimable) 。
然而,在特定情況下,即便是內核數據結構佔用的頁面也會被回收。
例如,文件系統元數據的緩存(in-memory)可以從存儲設備中重新讀取,因此,當系統存在內存壓力時,可以從主內存中丟棄它們。
釋放可回收物理內存頁並重新調整其用途的過程稱為 (surprise!) reclaim 。
Linux支持非同步或同步回收頁,取決於系統的狀態。
當系統負載不高時,大部分內存是空閑的,可以立即從空閑頁得到分配。
當系統負載提升後,空閑頁減少,當達到某個閾值( low watermark )時,內存分配請求將喚醒 kswapd 守護進程。它將以非同步的方式掃描內存頁。如果內存頁中的數據在其他地方也有,則釋放這些內存頁;或者退出內存到後置存儲設備(關聯 臟頁 )。
隨著內存使用量進一步增加,並達到另一個閾值- min watermark -將觸發回收。這種情況下,分配將暫停,直到回收到足夠的內存頁。
當系統運行時,任務分配並釋放內存,內存變得碎片化。
雖然使用虛擬內存可以將分散的物理頁表示為虛擬連續范圍,但有時需要分配大的連續的物理內存。這種需求可能會提升。例如,當設備驅動需要一個大的DMA緩沖區時,或當THP分配一個大頁時。
內存地址壓縮(compaction ) 解決了碎片問題。
該機制將佔用的頁從內存zone的下部移動到上部的空閑頁。壓縮掃描完成後,zone開始處的空閑頁就並在一起了,分配較大的連續物理內存就可行了。
與 reclaim 類似, compaction 可以在 kcompactd守護進程中非同步進行,也可以作為內存分配請求的結果同步進行。
在存在負載的機器上,內存可能會耗盡,內核無法回收到足夠的內存以繼續運行。
為了保障系統的其餘部分,引入了 OOM killer 。
OOM killer 選擇犧牲一個任務來保障系統的總體健康。選定的任務被killed,以期望在它退出後釋放足夠的內存以繼續正常的操作。
㈥ 如何將虛擬地址轉化成pfn,即頁幀號麻煩告訴我
用戶程序中的虛擬地址,靜態的是編譯時就定好的,動態的(如malloc),是通過內核的頁式內存管理在vma中定的,需要找到進程的task_struct中的頁表然後一級級的翻譯得到物理地址。
實際編譯時定的地址叫做邏輯地址,這個邏輯地址是需要段式管理來翻譯的,即段基址加段內偏移,由於linux中的這個段基址就是0,所以邏輯地址就直接對應了虛擬地址了。
這樣得到物理地址以後,首先將十六進制的低三位與成0,這樣做是得到頁的物理地址,這里指4K頁的情況,然後通過頁的物理地址減去mem_map的第一個元素的地址就得到了此頁在mem_map數組中的下標(此處注意指針相減不是指針表示的地址相減,而是兩者之間指針所表示元素類型的個數),此下標即為pfn,知道了在數組中的下標就可以得到struct page結構體了,就可以知道page的所有情況了。