1. 程式人生 > >linux內存管理概述

linux內存管理概述

按順序 使用 num 簡單 更多 未使用 滿足 由於 操作

其中介紹了虛擬內存的機制以及mmap系統調用的實現。mmap允許直接將設備內存映射到用戶進程的地址空間中。物理內存的管理,包括緩存的分配及回收,請頁機制,交換空間等。

技術分享圖片

1)交換模塊(swap)

這個模塊負責控制內存內容的換入換出,它通過替換機制,使得物理內存的頁框(RAM頁)中保留有效的邏輯頁,即從主存中淘汰最近沒被訪問的邏輯頁,保存近來訪問過的邏輯頁。該模塊實現的源程序分別是:

  • page_io.c的主要函數功能是讀寫交換文件。
  • swap_state.c的主要函數功能是修改交換高速緩存(swap cache)。
  • swapfile.c的主要函數功能是完成換入換出系統調用(sys_swapin、sys_swapon)。
  • swap.c的主要函數功能是定義交換使用的數據結構和常量,如free_page_low、free_page_high。
  • kswapd是一個周期性地處理交換內存和外村的守護進程(內核線程)

2)核心內存管理模塊

這個模塊負責核心內存管理功能,即對頁的管理,這些功能將被別的內核子系統(如文件系統)所使用。該模塊實現的源程序分別是:

  • page_alloc.c主要函數功能是處理頁的釋放、回收和分配
  • memory.c只要包含了請頁機制的相關函數。

3)結構特定的模塊

這個模塊負責給各種硬件平臺提供通用接口,這個模塊通過執行命令來改變硬件MMU的虛擬地址映射,並在發生頁錯誤時,提供公用的方法來通知別的內核子系統。這個模塊是實現虛擬內存的物理基礎。

技術分享圖片

首先內存管理程序通過映射機制把用戶程序的邏輯地址映射到物理地址,在用戶程序運行時如果發現程序要用的虛地址沒有對應的物理內存時,就發出了請求 也要求1;如果有空閑的內存可供分配,就請求分配內存2(於是用到了內存的分配二號回收),並把正在使用的物理頁記錄在頁緩存中3(使用了緩存機制)。如 果沒有足夠的內存可供分配,那麽就調用交換機制,騰出一部分內存4,5.另外在地址映射中要通過TLB(轉換後備緩沖區)來尋找物理頁8,交換機制中也要 用到交換緩存6,並且把物理頁內容交換到交換文件中後也要修改頁表來映射文件地址7.

1.1 內存地址類型和內存保護

1.1.1 地址類型

Linux是一個使用虛擬內存的系統,虛擬內存使運行在系統上的程序可以分配到比可用物理內存更多的空間。虛擬內存還能在進程地址空間上使用很多技巧,如映射設備的內存。

Linux系統使用幾種類型的地址,下面列出了Linux用到的地址類型概念。

1)用戶虛擬地址

該地址是用戶空間的程序使用的地址。根據硬件體系結構的不同,用戶地址可以是32位或64位,且每個進程擁有自己獨立的虛擬地址空間。

2)物理地址

該地址是物理內存的地址,在處理器和系統內存之間使用。物理地址是32或者64位。

3)總線地址

該地址是指總線的寄存器的地址,在外設總線和內存之間使用。

4)內核邏輯地址

內核邏輯地址組成了常規的內核地址空間,這些地址映射了大部分乃至所有的主內存,並被視為物理內存使用。在大多數的體系結構中,邏輯地址及其所關聯 的物理地址之間的區別,僅僅在於一個常數的偏移量。邏輯地址使用硬件特有的指針大小,所以,在配置有大量內存的32位系統上,僅通過邏輯地址可以無法尋址 所有的物理內存。在內核中,邏輯地址通常保存在unsigned long或者void*這樣的變量中。由kmalloc返回的內存就是邏輯地址。

通過宏可將邏輯地址與物理地址相互轉換,通過定義在<asm/page.h>中的宏__pa()返回與其關聯的物理地址,也可以使用__va()宏將物理地址映射回邏輯地址,但只能用於低端內存頁。

5)內存虛擬地址

由函數vmalloc分配的內存是虛擬地址,kmap函數也返回虛擬地址。這種地址卻不一定能直接映射到物理內存。它需通過一系列處理,如分配內存、地址轉換等,才能與邏輯地址聯系起來。虛擬地址通常保存在指針變量中。

6)低端內存和高端內存

低端內存和高端內存都是指物理內存。

  • 低端內存。低端內存代表存在於內存空間的邏輯地址的物理內存。大家遇到的幾乎都是低端內存。
  • 高端內存。高端內存是那些不存在邏輯地址的內存,高端內存常常是保留給用戶空間的進程頁。

在i386系統上,低端內存和高端內存的之間的界限通常設置為1GB。它是內核本身設置的限制,用於將32位地址空間分割為內核空間和用戶空間。

用戶空間可訪問4GB的虛擬線性內存空間。其中,0到3GB的虛擬內存地址是用戶空間,用戶進程可以直接對其進行訪問。從3GB到4GB的虛擬內存地址為內核空間,存放僅用於內核訪問的代碼和數據,是所有進程共享的,用戶不能對它進行操作。

所有的進程從3GB到4GB的虛擬空間都是一樣的,有同樣的頁目錄項和同樣的頁表,對應的同樣的物理內存段。這樣,內核態進程就共享代碼段和數據段。

1.1.2 內存保護

1)不同任務之間的保護

在80386上,通過賦予每個任務不同的虛擬—物理地址轉換映射,把每個任何放置在不同的虛擬地址完成。在30386上,每個任務都有自己的段表和頁表。

這種保護操作系統的方法是把操作系統存儲在虛擬地址空間的一個公共區域,然後,使每個任務按此區域分配一個同樣的虛擬地址空間,並進行同樣的虛擬-物理地址映射。各個任務公用的這部分虛擬地址空間被稱為全局地址空間。

只有一個任務占有的虛擬地址空間部分,即不被任何其他任務共享的虛擬地址部分,稱為局部地址空間。

在不同的任務中,對同一虛擬地址的訪問,實際上轉換為不同的物理地址。這就使得操作系統對每個任務的存儲器可以賦予相同的虛擬地址,仍然保證任務的 隔離。另一方面,對全局地址空間中同一虛擬地址的訪問,在所有任務中都轉換為同樣的物理地址,從而支持公共的代碼及數據的共享,例如,對操作系統的共享。

2)同一任務的保護

在一個任務之內,定義了四種執行特權級別,用來限制對任務中段進行訪問。在0級是操作系統內核,處理I/O,內存管理及其他關鍵的操作;在1級是系 統調用處理程序,用戶程序可以通過調用這裏的過程執行系統調用,但是,只有一些特定的和受保護的過程可以被調用;2級是庫過程;最後用戶程序運行在級別3 上,受到的保護最少。

1.2 80386的段頁式管理機制

80386以兩級虛擬-物理地址轉換,即使用了分段機制和分頁機制來實現兩級地址轉換的。第一級把包含段地址和段內偏移量的虛擬地址,轉換為一個線性地址。第二級把線性地址轉換為物理地址。

技術分享圖片

Linux內核中在物理內存中為每個進程維護一個頁表,頁表駐留在內存,不能被交換到磁盤。但是,內核使用一個獨立的頁表來管理頁表映射內核段,與用戶空間每個進程一個頁表不同,該頁表屬於整個內核,而且與當前正在運行的進程無關。

1.3 進程的內存組織

1.3.1 內存管理的數據結構

內存管理中有三個重要的數據結構struc vm_area_struct,struct page和struct mm_struct,它們用於表示進程的內存使用,它們與進程的關系如圖4.10所示。

技術分享圖片

每個進程都有一個struct mm_struct結構體,用來描述一個進程的虛擬內存。在結構體中包含了進程的頁表和許多其他的大量信息。

vm_area_struct結構描述進程的一個虛擬內存地址區域。一個VMA(虛擬內存區域)是對頁錯誤處理有同一規則的進程虛擬內存空間的部分,如共享庫、運行區域等,代表進程空間的一塊單獨連續的地址空間。

page結構用來描述一個物理頁,系統中每個物理頁有一個頁結構來保護跟蹤。

1.3.2 VMA在/proc文件系統中的顯示

VMA是Virtual Memory Aera的縮寫,即內存虛擬區域。在每個進程中,一般分為這幾個虛擬內存區域:程序的代碼區域(即text段);每種類型的數據對應一個區域,其中包括初 始化數據(在執行之處已經明確賦值的數據);未初始化的數據(BSS);程序棧等。

vma在程序運行中,不斷地由申請、清除、查找、分割、融合等對vma的管理操作,所有vma存在一個雙向鏈表中,另外還用AVL樹進行管理,以加速查找。

一個進程的內存區域可以從/proc/pid/maps中看到,/proc/*/maps中的每項都與一個vm_area_struct結構成員對應,可以用一個列表對每個字段進行描述。

例如:

技術分享圖片

每行中的字段說明如下:

  • start-end 虛擬內存區域的起始和結束地址
  • perm虛擬內存區域的讀、寫和執行許可的位掩碼。最後一個字符p表示是私有,s表示共享。
  • offset虛擬內存區域在被映射文件中的偏移(以頁為單位)。
  • major:minor主設備號和次設備號。
  • Image被映射的文件名字

VMA的應用是在頁面錯誤時產生調頁或換頁操作的。進程的所有VMA以一個排序的雙向鏈表來連接,按虛地址的下降順序來排列的,每個VMA對應一個相鄰的地址空間範圍。

1.4 虛擬內存管理

1.4.1 大容量對象緩存

內核提供了kmalloc和kfree,分配真實地址已知的實際物理內存塊,還提供vmalloc和vfree,用於對內核使用的虛擬內存進行分配和釋放。由kmalloc返回的內存更適合類似設備驅動的程序來使用。

當一個結構或表需要建立大容量對象實例時,常用vmalloc函數進行申請。

kmalloc分配的地址範圍一般在3G~high_memory,所以分配的物理地址與虛擬地址只有一個PAGE_OFFSET偏移,不需要為地址段修改頁表。而且分配的虛擬邏輯地址和物理地址都是連續的。

其中kmalloc分配的是物理地址,而返回的則是虛擬地址(通過一定的偏移量將物理地址轉換為虛擬地址)。

vmalloc函數分配的虛擬空間在3G+high_memory+VMALLOC_OFFSET以上高端,由vmlist鏈表管理。3GB是內核 態賴以訪問物理內存的地址,high_memory是安裝在計算機實際可用的物理內存的最高地址。VMALLOC_OFFSET則為長度為8MB的“隔離 帶”,起著越界保護作用。

vmalloc函數分配的虛擬空間管理結構列出如下:

技術分享圖片

在mm/vmalloc.c中有vmlist鏈表的全局變量申明struct vm_struct *vmlist,在vmlist鏈表中,每個虛擬內存塊之間都有個4KB大小的“隔離帶”,用來檢測訪問指針的越界錯誤。第一個節點的地址就是VMALLOC_START。

技術分享圖片

函數vmalloc的功能是分配與size相匹配頁面大小的連續虛擬內存,但對應的物理內存仍需經缺頁中斷後,由缺頁中斷服務程序分配,分配的物理頁是不連續的。函數vmalloc申請大塊緩沖區,但當前不用的內容不會調用入到物理內存中。

函數vmalloc列出如下:

void *vmalloc(unsigned long size)
{
    return __vmalloc(size,GFP_KERNEL|__GFP_HIGHMEM,PAGE_KERNEL);
}

函數__vmalloc分配足夠的頁數與size相配,把它們映射進連續的內核虛擬空間,但分配的內存塊不一定連續。在函數中第一步是在 vmlist中尋找一個大小合適的虛擬內存塊(get_vm_area(size))。第二步檢查這個虛擬塊是否可用(空閑),建立頁目錄,找到空閑(虛 擬塊映射內)分配給調用進程(get_free_page());如果虛擬塊是不可用的,那麽必須要釋放掉這個虛擬塊(vfree)。

1.4.2 內存映射

內存映射的介紹

運行可執行文件時,先被映射到進程的虛擬地址空間中,形成vm_area_struct結構鏈表,接著程序的一部分被操作系統裝入到物理內存。這種將映像鏈接到進程虛擬地址空間的訪問稱為“內存映射”。通過內存映射,文件的內容被直接鏈接到進程的虛擬地址空間。

隨著vm_area_struct結構的生成,這些結構所描述的虛擬內存區域上的標準操作函數也由Linux初始化。在邏輯地址和物理地址之間相互 轉換的工作,是由內核和硬件內存管理單元(MMU)共同完成的,MMU是CPU的一個部分。內核告訴MMU如何為每個進程把邏輯嚴密映射到某特定物理頁 面,而MMU在進程提出內存請求時完成實際的轉換工作。為了減少開銷,最近被執行的地址轉換結果將被存儲在MMU的轉換後備緩存(TLB)內。除了由於內 核的操作致使TLB無效偶爾會通知CPU外,Linux不會明確管理TLB。

內核維護了一個或者更多由struct page項構成的數組,它們跟蹤系統上所有的物理內存。

一些函數和宏可用來在struct page和虛擬地址之間進行轉換:

  • struct page *virt_to_page(void *kaddr):這個宏接受一個內核邏輯地址,並返回與其關聯的struct page指針。因為它需要一個邏輯地址,它對vmalloc返回的內存和高端內存無效。
  • void *page_address(struct page *page):如果這個地址存在,則返回該頁的內核虛擬地址。對於高端內存,僅在該頁已經被映射的情況下,其地址才存在。

在移動或修改頁表時,應該持有該page_table_lock自旋鎖。

與內存映射相關的幾個文件在mm目錄下,其中,mmap.c文件中主要函數do_mmap的功能是:把文件中的邏輯地址映射成虛存的線性地址,即把 從文件結構中得到的邏輯地址轉換為vm_area_struct結構所需的地址。mremap.c文件中主要函數sys_mremap的功能是:擴張或縮 小現存的虛擬內存空間。filemap.c文件中的主要函數功能是:處理內存映射和也高速緩存器,即把線性地址空間映射到內存且修改頁高速緩存。這部分含 有從磁盤讀寫的I/O操作。

sys_brk系統調用

sys_brk提供支持C語言的malloc和free函數低級操作。C庫函數malloc通過系統調用sys_brk向內核申請了一段虛地址空間vma來建立地址映射,vma全部建立起與內存頁的映射,C庫函數free也通過這個系統調用告訴內存需要收回這段地址空間,取消已建立的映射。sys_brk系統調用可以對用戶進程的堆的大小進行操作,使堆擴展或者縮小。

sys_brk中調用do_brk函數,它是一個簡單的do_mmap函數,它僅處理匿名映射,並將vma全部映射到內存頁,而不像do_mmap函數是由缺頁來引起調頁的。

mmap系統調用

一個進程可通過系統調用mmap(),將一個已經打開文件的內容映射到它的用戶空間。它直接調用do_mmap()函數來完成映射,在這個函數中, 參數file為映射的文件,參數addr為映射的地址,參數len為VMA長度,長度prot指定該vma段的訪問權限,參數flag為vma段的屬性。

1.4.3 虛擬內存的加鎖和保護

Linux可對虛擬內存段中的任一部分加鎖或保護。對進程的虛擬地址加鎖,其實質就是對vma段的vm_flags屬性“或”上 VM_LOCKED。在虛存加鎖後,它對應的物理頁面駐留內存,不再被頁面置換程序換出。除非調用mlock的進程終止或者調用exec執行其他程序,這 部分被鎖住的源碼才被釋放。通過fork()調用所創建子進程不能夠繼承由父進程調用mlock鎖住的頁面。加鎖操作有四個系統調用函數:mlock、 munlock、mlockall和munlockall。

1.5 物理內存管理

NUMA(非一致內存訪問體系)系統保持了物理上分散、而邏輯上同一的內存模式,是高性能服務器的主流體系結構。

1.5.1 夥伴系統

夥伴系統的概述

Linux內核內存管理的一項重要工作就是如何在頻繁申請釋放內存的情況下,避免碎片的產生。Linux采用夥伴系統解決外部碎片的問題,采用slab解決內部碎片的問題, 在這裏我們先討論外部碎片問題。避免外部碎片的方法有兩種:一種是之前介紹過的利用非連續內存的分配;另外一種則是用一種有效的方法來監視內存,保證在內 核只要申請一小塊內存的情況下,不會從大塊的連續空閑內存中截取一段過來,從而保證了大塊內存的連續性和完整性。顯然,前者不能成為解決問題的普遍方法, 一來用來映射非連續內存線性地址空間有限,二來每次映射都要改寫內核的頁表,進而就要刷新TLB,這使得分配的速度大打折扣,這對於要頻繁申請內存的內核 顯然是無法忍受的。因此Linux采用後者來解決外部碎片的問題,也就是著名的夥伴系統。

什麽是夥伴系統?

夥伴系統的宗旨就是用最小的內存塊來滿足內核的對於內存的請求。在最初,只有一個塊,也就是整個內存,假如為1M大小,而允許的最小塊為64K,那麽當我 們申請一塊200K大小的內存時,就要先將1M的塊分裂成兩等分,各為512K,這兩分之間的關系就稱為夥伴,然後再將第一個512K的內存塊分裂成兩等 分,各位256K,將第一個256K的內存塊分配給內存,這樣就是一個分配的過程。

物理頁面的分配和回收

Linux使用夥伴算法來有效地分配與回收頁面塊。頁面分配時,內存分配器在free_area數組中尋找一個與請求大小相同的空閑塊。分配算法首 先搜尋滿足請求大小的頁面。它從free_area數據結構的list域著手沿著鏈來搜索空閑頁面。如果沒有以這樣的方式請求大小的空閑頁面,則它搜索兩 倍於請求大小的內存塊。這個過程一直將持續到free_area被搜索完或找到滿足要求的內存塊為止。

如果找到的頁面大於請求的塊,則把它分成兩塊:一個與請求塊匹配,另一個事空閑塊。每塊大小都是2的N次冪。空閑塊被鏈接進相應大小的隊列,而另一個頁面塊被分配給調用者。

頁面回收時,內存分配器將檢查是否有相同大小的相鄰或者夥伴內存塊存在。如果有,則將它們結合起來形成一個大小為原來兩倍的新空閑塊。每次結合完之後,代碼還要檢查是否可以繼續合並成更大的頁面。

_get_free_page函數是申請空閑頁面的上層接口。free_pages函數釋放內存頁。

下面我們結合示意圖來了解夥伴系統分配和回收內存塊的過程。

技術分享圖片

1 初始化時,系統擁有1M的連續內存,允許的最小的內存塊為64K,圖中白色的部分為空閑的內存塊,著色的代表分配出去了得內存塊。

2 程序A申請一塊大小為34K的內存,對應的order為0,即2^0=1個最小內存塊

2.1 系統中不存在order 0(64K)的內存塊,因此order 4(1M)的內存塊分裂成兩個order 3的內存塊(512K)

2.2 仍然沒有order 0的內存塊,因此order 3的內存塊分裂成兩個order 2的內存塊(256K)

2.3 仍然沒有order 0的內存塊,因此order 2的內存塊分裂成兩個order 1的內存塊(128K)

2.4 仍然沒有order 0的內存塊,因此order 1的內存塊分裂成兩個order 0的內存塊(64K)

2.5 找到了order 0的內存塊,將其中的一個分配給程序A,現在夥伴系統的內存為一個order 0的內存塊,一個order

1的內存塊,一個order 2的內存塊以及一個order 3的內存塊

3 程序B申請一塊大小為66K的內存,對應的order為1,即2^1=2個最小內存塊,由於系統中正好存在一個order 1的內

存塊,所以直接用來分配

4 程序C申請一塊大小為35K的內存,對應的order為0,同樣由於系統中正好存在一個order 0的內存塊,直接用來分

5 程序D申請一塊大小為67K的內存,對應的order為1

5.1 系統中不存在order 1的內存塊,於是將order 2的內存塊分裂成兩塊order 1的內存塊

5.2 找到order 1的內存塊,進行分配

6 程序B釋放了它申請的內存,即一個order 1的內存塊

7 程序D釋放了它申請的內存

7.1 一個order 1的內存塊回收到內存當中

7.2由於該內存塊的夥伴也是空閑的,因此兩個order 1的內存塊合並成一個order 2的內存塊

8 程序A釋放了它申請的內存,即一個order 0的內存塊

9 程序C釋放了它申請的內存

9.1 一個order 0的內存塊被釋放

9.2 兩個order 0夥伴塊都是空閑的,進行合並,生成一個order 1的內存塊m

9.3 兩個order 1夥伴塊都是空閑的,進行合並,生成一個order 2的內存塊

9.4 兩個order 2夥伴塊都是空閑的,進行合並,生成一個order 3的內存塊

9.5 兩個order 3夥伴塊都是空閑的,進行合並,生成一個order 4的內存塊

1.5.2 緩存及slab

緩存的結構

一個對象的所有實例都存在同一緩存區中。不同的對象,放在不同的緩存區中。每一緩存區有若幹個slab,按照滿、半滿、空的順序排列。整個內核內存可看做是按照這種緩存區組織的。

在內核中緩存的全局變量malloc_size[]中定義了可分配對象的大小,每次分配對象時,只能分配成cache_sizes結構描述大小的對象。

slab結構

slab塊是內核內存分配與頁面級分配的接口。一種對象的所有實例在同一個緩沖區,每個緩沖區存在若幹個slab塊,按滿、半滿和空閑的順序。每個slab塊的大小為頁面大小的整數倍,存有若幹個對象。

slab算法基於對象緩存,就是保留對象初始化狀態的不變部分。當新建一個對象時,如果在緩沖區中有空閑的這個對象位置時,就獲得此對象,不必初始 化。當釋放對象時,只是在緩存中將相應位置標為空閑,而不做析構。只是在系統資源不足時,slab算法才將一部分未使用的緩存空間釋放。這樣減少了大量常 用對象的初始化和析構部分的費時過程。

1.5.3 交換空間

內存管理系統需要將暫時不用的內存數據轉儲到外存中,Linux采用兩種方式保存換出的頁面:一種用整個設備,如硬盤的一個分區,稱為變換設備;另 一種用文件系統中固定長度的文件,稱為交換文件。它們統稱為交換空間。這兩種交換方式的內部格式是一致的。前4096字節是一個以字符口串“SWAP- SPACE”結尾的位圖。位圖的每一位對應一個交換空間的頁面,置位表示對應的頁面可用於換頁操作。每4096字節之後才是真正存放換出頁面的空間。這 樣,每個交換空間最多可容納(4096-10)*8-1=32687個頁面。

交換設備比交換文件有效很多。在交換設備中,屬於同一頁面的數據塊總是連續存放的。第一個數據塊一經確定,後續的數據塊可以按順序讀出或寫入。而在 交換文件中,屬於同一頁面的數據雖然在邏輯上是連續的(在交換文件的位圖看來),但數據塊的實際位置可能是零散的,需要通過交換文件的inode檢索,這 決定於擁有交換文件的文件系統。

交換文件是在物理磁盤上的,因此將所有的交換頁面按簇為單位存儲,每簇內的頁面連續存放。

1.5.4 請頁機制

頁故障錯誤的產生有三種原因:

  • 程序出現錯誤,虛擬內存無效(還沒有映射,即還沒有映射到磁盤上),Linux將向進程發送SIGSEGV信號並終止進程的運行;
  • 虛擬內存有效,但其所有對應的頁當前不在物理內存中(但是存在磁盤中),即缺頁錯誤,這時,操作系統必須從磁盤映像(頁未分配或屬於共享庫)或交換文件(此頁被換出)中將其裝入物理內存。
  • 最後一種情況是,要訪問的虛地址被寫訪問(對只讀區域進行寫操作),即保護錯誤。

對有效的虛擬地址,即已分配的虛擬地址,如果“缺頁”錯誤的話,Linux必須區分頁所在的位置,即判斷頁是在交換文件中,還是在可執行映像中。 Linux通過頁表項中的信息區分頁面所在的位置。如果該頁的頁表項是無效的,但非空,則說明該頁處於交換文件中,操作系統要從交換文件裝入頁,如果都不 在,則把虛擬地址映射到物理內存。

1.5.5 守護進程kswapd

當物理內存出現不足時,Linux內存管理子系統需要釋放部分物理內存頁。這一任務由內核的交換守護進程kswapd完成,該內核守護進程實際是一 個內核線程,它在內核初始化時啟動,並周期性地運行。它的任務就是保證系統中具有足夠的空閑頁,從而使內存管理子系統能夠有效運行。

技術分享圖片

1.5.6 內存管理相關的高速緩存

技術分享圖片

linux內存管理概述