linux內存

1 內存尋址
  1.1 物理地址、虛擬地址以及線性地址
  1.2 段機制
  1.3 linux中的段機制
  1.4 分頁機制
    1.4.1 為什么使用兩級分頁
    1.4.2 兩級頁表結構
    1.4.3 頁面高速緩存
  1.5 linux分頁機制
2 內存管理
  2.1 虛擬內存、內核空間和用戶空間
  2.2 虛擬內存實現機制之間的關系
  2.3 進程用戶空間管理
    2.3.1 進程用戶空間簡述
    2.3.2 進程用戶空間創建
    2.3.3 虛存映射
  2.4 請頁機制
    2.4.1 請求調頁
    2.4.2 寫時復制
  2.5 物理頁的分配和回收
    2.5.1 伙伴算法
    2.5.2 slab機制分配
  2.6 交換機制
    2.6.1 哪種頁面被換出
    2.6.2 如何在交換區中存放頁面
    2.6.3 如何選擇被交換出的頁面
    2.6.4 頁面交換守護進程kswapd
3 其他
  3.1 Linux內存相關命令
    3.1.1 free
    3.1.2 /proc/meminfo
    3.1.3 vmstat
    3.1.4 top
    3.1.5 pmap
    3.1.6 ps
  3.2 overcommit

1 內存尋址

1.1 物理地址、虛擬地址以及線性地址

  • 物理地址: 物理內存的內存單元地址
  • 虛擬地址: 程序員看到的內存空間定義未虛擬地址,intel X86 CPU尋址使用了段機制,最初的8086中有4個16位的段寄存器:CS、DS、SS、ES,分別用于存放可執行代碼的代碼段、數據段、堆棧段和其他段的基地址,解決了CPU數據總線16位尋址20位數據地址空間的問題。 虛擬地址一般用“段:偏移量”的形式來描述,比如在8086中A815:CF2D就代表段首地址為A815,段內偏移位為CF2D的虛地址。
  • 線性地址: 是指一段連續的,不分段的,范圍為0到4GB的地址空間,一個線性地址就是線性地址空間的一個絕對地址。

尋址模式有2種:

  1. 實模式: 是 段地址+偏移量 的方式,得到物理地址;如當程序執行“mov ax,[1024]”這樣一條指令時,在8086的實模式下,把某一段寄存器(比如ds)左移4位,然后與16位的偏移量(1024)相加后被直接送到內存總線上,這個相加后的地址就是內存單元的物理地址,而程序中的地址(例如ds:1024)就叫虛擬地址

  2. 保護模式:不 允許通過段寄存器取值得到段的起始地址,而是把虛擬地址轉進一個 MMU 的硬件,經過額外的轉換和檢查,進而得到一個物理地址,如下圖所示:

    保護模式下尋址

MMU是一種硬件電路,它包含兩個部件,一個是分段部件,一個是分頁部件,支持分段機制和分頁機制。分段機制把一個虛擬地址轉換為線性地址;接著,分頁機制把一個線性地址轉換為物理地址,如下圖所示:

MMU將虛擬地址轉換為物理地址

1.2 段機制

段是虛擬地址空間的基本單位,段機制將虛擬地址空間地址轉換為線性地址空間的一個線性地址。
段描述符包含下面的信息:

  1. 段的基地址:在線性空間段中的起始地址;
  2. 段的界限:在虛擬地址空間內,段內可以使用的最大偏移量;
  3. 段的保護屬性:如段是否可以被寫入或者讀出、段的特權級別等等

下圖說明了虛擬地址空間和線性地址空間的映射關系:

虛擬-線性地址空間映射

在虛擬地址轉換為線性地址過程中,會做如下檢查:

  • 段內偏移如果大于段界限,系統講產生異常;
  • 如果對一個段進行訪問,系統會根據段的保護屬性檢查訪問者是否具有訪問權限,如果沒有,則產生異常。

1.3 linux中的段機制

intel段機制是從8086引入,最初是為了解決CPU內部16位地址到20位實地址的轉換。為了保持兼容性,386仍然使用了段機制。linux目前所有的進程都使用了相同的邏輯空間地址,由于絕大多數硬件平臺都不支持段機制,只支持分頁機制,所以為了讓Linux具有更好的可移植性,linux需要去掉段機制而只使用分頁機制。 然后80x86系列CPU必須使用段機制,不能繞過段機制直接給出線性地址空間地址,linux上讓段的基地址為0,在32位系統下段界限為4G, 使用這種巧妙的訪問繞過了段機制。
  另外80X86要求必須為數據段和代碼段創建不同的段,不僅如此,linux內核運行在特權級別0,和用戶程序運行在特權級別3,根據80x86的段保護機制,特權級3的程序無法訪問特權級別0的段,所以linux必須為內核和用戶程序分別創建獨立的數據段和代碼段;linux內核不區分數據段和堆棧段,同時也只使用了段的2個保護級別,簡化的段底層復雜的設計。
  linux這樣使用段機制違背了段最初的設計初衷:不同的段映射到不用的線性地址空間中。 linux上段使用了完全相同線性地址空間,他們可以相互覆蓋,這樣線性地址空間映射到物理地址空間,修改任何一個段的數據都會影響其他段。linux首先利用的段的特權級別保護內核段不會被用戶程序訪問和修改,其次引入了分頁機制保護了段數據。

1.4 分頁機制

分頁機制再分段機制之后進行,完成線性地址到物理地址轉換的過程。如果不允許分頁,段機制轉換的線性地址就是物理地址;如果允許分頁機制,那么線性地址就需要通過分頁機制找到物理地址。
  線性地址空間被劃分為若干塊大小相等的片,稱為頁,并把各頁編號,同樣的,物理地址也被劃分為若干塊大小相等的片。線性地址頁和物理頁有映射關系,如下圖所示:

線性地址頁-物理地址頁對應關系

那么,頁的大小應該為多少?頁過大或過小都會影響內存的使用率。其大小在設計硬件分頁機制時就必須確定下來,例如80X86支持的標準頁大小為4KB(也支持4MB)。

1.4.1 為什么使用兩級分頁

假設每個進程都需要占用4GB的線性地址空間,那么就需要1M個頁表,每個頁表需要4字節的描述信息,這樣每個進程頁表就得占用4M的空間,為了減少頁表空間占用,使用了兩級分頁機制,每個進程都被分配一個頁目錄,只有被使用到頁表才會分配到內存中。一級頁表需要一次分配所有頁表空間,兩級頁表則可以在需要的時候再分配頁表空間。

1.4.2 兩級頁表結構

兩級頁表第一級成為頁目錄,存儲在一個4K字節的頁中。頁目錄有1K個表項,每個表項4個字節,并指向第二級表。線性地址包含一級索引、二級索引和偏移。 一級索引占線性地址的高10位,指向1k個頁目錄,二級索引占用中間10位,指向了物理地址的頁表項,最后12位指向了物理頁的偏移,如下圖描述:

兩級頁表結構

其中,寄存器CR3中存儲了頁目錄的起始地址。

這里比較巧妙的地方是頁都是4K的整數倍,所以低12位都是0, 利用這低12位可以存儲頁面的屬性信息,如下所述:

  • 第0位是存在位,如果P=1,表示頁表地址指向的該頁在內存中,如果P=0,表示不在內存中。
  • 第1位是讀/寫位,第2位是用戶/管理員位,這兩位為頁目錄項提供硬件保護。當特權級為3的進程要想訪問頁面時,需要通過頁保護檢查,而特權級為0的進程就可以繞過頁保護。
  • 第3位是PWT(Page Write-Through)位,表示是否采用寫透方式,寫透方式就是既寫內存(RAM)也寫高速緩存,該位為1表示采用寫透方式
  • 第4位是PCD(Page Cache Disable)位,表示是否啟用高速緩存,該位為1表示啟用高速緩存。
  • 第5位是訪問位,當對頁目錄項進行訪問時,A位=1。
  • 第7位是Page Size標志,只適用于頁目錄項。如果置為1,頁目錄項指的是4MB的頁面。
  • 第9~11位由操作系統專用,Linux也沒有做特殊之用。

1.4.3 頁面高速緩存

兩級分頁信息都是存儲再內存中的,這樣CPU每取一個物理數據,都必須經過至少2次內存訪問,大大降低的訪問速度。為了提高速度,在80X86中設置一個最近存取頁的高速緩存硬件機制,它自動保持32項處理器最近使用的頁表項,因此,可以覆蓋128K字節的內存地址。當訪問線性地址空間的某個地址時,先檢查對應的頁表項是否在高速緩存中,如果在,就不必經過兩級訪問了,如果不在,再進行兩級訪問。平均來說,頁面高速緩存大約有90%的命中率,也就是說每次訪問存儲器時,只有10%的情況必須訪問兩級分頁機構。過程如下下圖所示:

頁面高速緩存

1.5 linux分頁機制

為了兼容32和64位系統,linux提供了3級分頁機制:

  • 頁總目錄PGD(Page Global Directory)
  • 頁中間目錄PMD(Page Middle Directory)
  • 頁表PT(Page Table)
    具體尋找邏輯圖如下:
linux三級分頁

??盡管Linux采用的是三級分頁模式,但我們的討論還是以80X86的兩級分頁模式為主,因此,Linux忽略中間目錄層,以后,我們把頁總目錄就叫頁目錄。
??每一個進程有它自己的頁目錄和自己的頁表集。當進程切換發生時,Linux把cr3控制寄存器的內容保存在前一個執行進程的PCB中,然后把下一個要執行進程的PCB的值裝入cr3寄存器中。因此,當新進程恢復在CPU上執行時,分頁單元指向一組正確的頁表。

2 內存管理

2.1 虛擬內存、內核空間和用戶空間

linux簡化了分段機制,使得虛擬地址和線性地址一致,線性地址空間在32位系統上固定為4GB大小,linux的虛擬地址空間也這么大大,啟動最高1G因為內核空間,剩余的3G為用戶空間,內核空間為共享,每個進程都可以通過系統調用進入內核空間。對于具體每個進程來說都擁有4GB的虛擬空間。下圖給出進程虛擬空間示意圖:

進程虛擬地址空間

??用戶空間是進程隔離的,不用用戶空間上相同的虛擬地址實際物理內存是不一樣的,一個CPU同一時刻只會執行一個進程,在CPU眼中,只存在一個虛擬地址空間,進程切換的時候屬于進程的頁表也會發生變化,由于虛擬地址和物理地址映射主要是頁表機制實現的,頁表的變化也意味著物理地址空間的變化。
??linux內核雖然占據了虛擬地址空間的高位,但是映射到物理內存地址空間卻是從最低位開始的,示意圖如下所示:

內核虛擬地址空間到物理地址空間映射

2.2 虛擬內存實現機制之間的關系

linux虛擬內存實現需要多種機制支持,核心機制如下:

  • 地址映射機制
  • 請頁機制
  • 內存回收和分配機制
  • 交換機制
  • 緩存和刷新機制

這幾種機制關系如下圖所示:

linux虛擬內存實現主要機制關系

首先內核通過地址映射機制將進程虛擬地址空間映射到物理地址空間,當進程運行時,如果要使用某個頁但是這個頁沒有建立和物理內存頁的關系,就需要請頁;如果有空閑內存使用,就會進行內存分配和回收,并且進行講物理頁緩存起來;如果沒有足夠的內存使用,就使用交換機制,騰出一部分內存;另外在地址映射中要通過TLB來尋找物理頁;交換機制中也要用到交換緩存,并且把物理頁內容交換到交換文件中后也要修改頁表來映射文件地址。

2.3 進程用戶空間管理

2.3.1 進程用戶空間簡述

下圖是一個進程用戶空間的簡單劃分:

進程用戶空間

??進程的地址都是虛擬地址,只會為真正使用的頁面映射物理內存。一個進程的用戶地址空間主要由兩個數據結來描述。一個是mm_struct結構,它對進程整個用戶空間進行描述,簡稱內存描述符;另一個是vm_area_structs結構,它對用戶空間中各個區間(簡稱虛存區進行描述,這里的虛存區就是上例中的代碼區,未初始化數據區,數據區以及堆棧區等)。
??把虛存區劃分成一個個空間的原因是這些虛存區的來源不一樣,有的來源于可執行文件映象,有的來自與共享庫,而有的可能是動態分配的內存區,對不同區間可能有不同的訪問權限和操作。下圖簡單說明了虛存區的操作:

虛存區操作

??內核內部關于進程空間管理數據結果的關系如下圖所示:

相關數據結構關系圖

2.3.2 進程用戶空間創建

進程的用戶空間是在執行系統調用的fork時創建的,基于寫時復制的原理,子進程創建的時候繼承了父進程的用戶空間,僅僅是mm_struc結構的建立、vm_area_struct結構的建立以及頁目錄和頁表的建立,并沒有真正地復制一個物理頁面,這也是為什么Linux內核能迅速地創建進程的原因之一。

2.3.3 虛存映射

當調用exec()系統調用開始執行一個進程時,進程的可執行映像(包括代碼段、數據段等)必須裝入到進程的用戶地址空間。如果該進程用到了任何一個共享庫,則共享庫也必須裝入到進程的用戶空間。Linux并不將映像裝入到物理內存,相反,可執行文件只是被連接到進程的用戶空間中。隨著進程的運行,被引用的程序部分會由操作系統裝入到物理內存,這種將映像鏈接到進程用戶空間的方法被稱為“虛存映射”,也就是把文件從磁盤映射到進程的用戶空間,這樣把對文件的訪問轉化為對虛存區的訪問。有兩種類型的虛存映射:

  • 共享的:有幾個進程共享這一映射,也就是說,如果一個進程對共享的虛存區進行寫,其它進程都能感覺到,而且會修改磁盤上對應的文件。
  • 私有的:進程創建的這種映射只是為了讀文件,而不是寫文件,因此,對虛存區的寫操作不會修改磁盤上的文件,由此可以看出,私有映射的效率要比共享映射的高。

除了這兩種映射外,如果映射與文件無關,就叫匿名映射。
當某個可執行映像映射到進程用戶空間中并開始執行時,因為只有很少一部分虛存頁面裝入到了物理內存,可能會遇到所訪問的數據不在物理內存。這時,處理器將向Linux 報告一個頁故障及其對應的故障原因,于是就用到了后面講述的請頁機制。

2.4 請頁機制

當一個進程執行時,如果CPU訪問到一個有效的虛地址,但是這個地址對應的頁沒有在內存,則CPU產生一個缺頁異常。如果這個虛存區的訪問權限與引起缺頁異常的訪問類型相匹配,則調用handle_mm_fault()函數,該函數確定如何給進程分配一個新的物理頁面:

  • 如果被訪問的頁不在內存,也就是說,這個頁還沒有被存放在任何一個物理頁面中,那么,內核分配一個新的頁面并適當地初始化;這種技術稱為“請求調頁”。
  • 如果被訪問的頁在內存但是被標為只讀,也就是說,它已經被存放在一個頁面中,那么,內核分配一個新的頁面,并把舊頁面的數據拷貝到新頁面來初始化它;這種技術稱為“寫時復制”。

2.4.1 請求調頁

“請求調頁”指的是一種動態內存分配技術,它把頁面的分配推遲到不能再推遲為止,也就是說,一直推遲到進程要訪問的頁不在物理內存時為止,由此引起一個缺頁異常。
??請求調頁技術的引入主要是因為進程開始運行時并不訪問其地址空間中的全部地址;事實上,有一部分地址也許進程永遠不使用。此外,程序的局部性原理保證了在程序執行的每個階段,真正使用的進程頁只有一小部分,因此臨時用不著的頁根本沒必要調入內存。
??但是,系統為此也要付出額外的開銷,這是因為由請求調頁所引發的每個“缺頁”異常必須由內核處理,這將浪費CPU的周期。幸運的是,局部性原理保證了一旦進程開始在一組頁上運行,在接下來相當長的一段時間內它會一直停留在這些頁上而不去訪問其它的頁:這樣我們就可以認為“缺頁”異常是一種稀有事件。

2.4.2 寫時復制

寫時復制(Copy-on-write)是一種可以推遲甚至免除拷貝數據的技術。內核此時并不復制整個進程空間,而是讓父進程和子進程共享同一個拷貝。只有在需要寫入的時候,數據才會被復制,從而使各個進程擁有各自的拷貝。也就是說,資源的復制只有在需要寫入的時候才進行,在此之前,以只讀方式共享。這種技術使地址空間上的頁的拷貝被推遲到實際發生寫入的時候。有時共享頁根本不會被寫入,例如,fork()后立即調用exec(),就無需復制父進程的頁了。fork()的實際開銷就是復制父進程的頁表以及給子進程創建唯一的PCB。這種優化可以避免拷貝大量根本就不會使用的數據

2.5 物理頁的分配和回收

從虛擬內存的角度來看,頁就是最小單位。體系結構不同,支持的頁大小也不盡相同。有些體系結構甚至支持幾種不同的頁大小。大多數32位體系結構支持4KB的頁,而64位體系結構一般會支持8KB的頁。這就意味著,在支持4KB頁大小并有1GB物理內存的機器上,物理內存會被劃分為262144個頁。內核用struct page結構表示系統中的每個物理頁,也叫頁描述符。
??頁描述符中比較重要的域包括:

  • flag域,用來存放頁面的狀態,這些狀態包括頁是不是臟的,是不是被鎖定在內存中等等;
  • _count域, 存放頁的引用計數—也就是這一頁被引用了多少次。當計數值變為0時,就說明當前內核并沒有引用這一頁,于是,在新的分配中就可以使用它;
  • virtual域, 頁的虛擬地址;
  • lru域, 存放的next和prev指針,指向最近最久未使用(LRU)鏈表中的相應結點,這個鏈表用于頁面的回收。

必須要理解的一點是page結構與物理頁相關,而并非與虛擬頁相關。因此,該結構對頁的描述只是短暫的。即使頁中所包含的數據繼續存在,但是由于交換等原因,它們可能并不再和同一個page結構相關聯。內核僅僅用這個數據結構來描述當前時刻在相關的物理頁中存放的東西。這種數據結構的目的在于描述物理內存本身,而不是描述包含在其中的數據。
??隨著用戶程序的執行和結束,就需要不斷地為其分配和釋放物理頁面。內核應該為分配一組連續的頁面而建立一種穩定、高效的分配策略。但是,頻繁地請求和釋放不同大小的一組連續頁面,必然導致在已分配的內存塊中分散許多小塊的空閑頁面,即外碎片,由此帶來的問題是,即使這些小塊的空閑頁面加起來足以滿足所請求的頁面,但是要分配一個大塊的連續頁面可能就根本無法滿足。為此,Linux采用著名的伙伴(Buddy)算法來解決外碎片問題。

2.5.1 伙伴算法

Linux的伙伴算法把所有的空閑頁面分為10個塊鏈表,每個鏈表中的一個塊含有2的冪次個頁面,我們把這種塊叫做“頁塊”或簡稱“塊”。例如,第0個鏈表中塊的大小都為20(1個頁面),第1個鏈表中塊的大小為都為21(2個頁面),第9個鏈表中塊的大小都為2^9(512個頁面)。
??假設要求分配的塊其大小為128個頁面。該算法先在塊大小為128個頁面的鏈表中查找,看是否有這樣一個空閑塊。如果有,就直接分配;如果沒有,該算法會查找下一個更大的塊,具體地說,就是在塊大小為256個頁面的鏈表中查找一個空閑塊。如果存在這樣的空閑塊,內核就把這256個頁面分為兩等份,一份分配出去,另一份插入到塊大小為128個頁面的鏈表中。如果在塊大小為256個頁面的鏈表中也沒有找到空閑頁塊,就繼續找更大的塊,即512個頁面的塊。如果存在這樣的塊,內核就從512個頁面的塊中分出128個頁面滿足請求,然后從384個頁面中取出256個頁面插入到塊大小為256個頁面的鏈表中。然后把剩余的128個頁面插入到塊大小為128個頁面的鏈表中。如果512個頁面的鏈表中還沒有空閑塊,該算法就放棄分配,并發出出錯信號。
??以上過程的逆過程就是塊的釋放過程,這也是該算法名字的來由。滿足以下條件的兩個塊稱為伙伴:

  • 兩個塊的大小相同
  • 兩個塊的物理地址連續
  • 兩個快必須是從同一個更大的塊中分離出來

伙伴算法把滿足以上條件的兩個塊合并為一個塊,該算法是迭代算法,如果合并后的塊還可以跟相鄰的塊進行合并,那么該算法就繼續合并。

2.5.2 slab機制分配

Slab有solaris操縱系統引入,主要是基于以下考慮:

  • 內核對內存區的分配取決于所存放數據的類型。例如,當給用戶態進程分配頁面時,內核調用__get_free_pages()函數,并用0填充所分配的頁面。而給內核的數據結構分配頁面時,事情沒有這么簡單,例如,要對數據結構所在的內存進行初始化、在不用時要收回它們所占用的內存。因此,Slab中引入了對象這個概念,所謂對象就是存放一組數據結構的內存區,其方法就是構造或析構函數,構造函數用于初始化數據結構所在的內存區,而析構函數收回相應的內存區。但為了便于理解,也可以把對象直接看作內核的數據結構。為了避免重復初始化對象,Slab分配模式并不丟棄已分配的對象,而是釋放但把它們依然保留在內存中。當以后又要請求分配同一對象時,就可以從內存獲取而不用進行初始化。

Linux中對Slab分配模式有所改進,它對內存區的處理并不需要進行初始化或回收。出于效率的考慮,Linux并不調用對象的構造或析構函數,而是把指向這兩個函數的指針都置為空。Linux中引入Slab的主要目的是為了減少對伙伴算法的調用次數。
??實際上,內核經常反復使用某一內存區。例如,只要內核創建一個新的進程,就要為該進程相關的數據結構(PCB、打開文件對象等)分配內存區。當進程結束時,收回這些內存區。因為進程的創建和撤銷非常頻繁,因此,Linux的早期版本把大量的時間花費在反復分配或回收這些內存區上。從Linux2.2開始,把那些頻繁使用的頁面保存在高速緩存中并重新使用。
??可以根據對內存區的使用頻率來對它分類。對于預期頻繁使用的內存區,可以創建一組特定大小的專用緩沖區進行處理,以避免內碎片的產生。對于較少使用的內存區,可以創建一組通用緩沖區來處理,即使這種處理模式產生碎片,也對整個系統的性能影響不大。
??硬件高速緩存的使用,又為盡量減少對伙伴算法的調用提供了另一個理由,因為對伙伴算法的每次調用都會“弄臟”硬件高速緩存,因此,這就增加了對內存的平均訪問次數。
??Slab分配模式把對象分組放進緩沖區,因為緩沖區的組織和管理與硬件高速緩存的命中率密切相關,因此,Slab緩沖區并非由各個對象直接構成,而是由一連串的“大塊(Slab)”構成,而每個大塊中則包含了若干個同種類型的對象,這些對象或已被分配,或空閑,如圖4.12所示。一般而言,對象分兩種,一種是大對象,一種是小對象。所謂小對象,是指在一個頁面中可以容納下好幾個對象的那種。例如,一個inode結構大約占300多個字節,因此,一個頁面中可以容納8個以上的inode結構,因此,inode結構就為小對象。Linux內核中把小于512字節的對象叫做小對象,大于512字節的對象叫做大對象。

slab緩存

實際上,緩沖區就是主存中的一片區域,把這片區域劃分為多個塊,每塊就是一個Slab,每個Slab由一個或多個頁面組成,每個Slab中存放的就是對象。Linux把緩沖區分為專用和通用,它們分別用于不同的目的:

  • 專用緩沖區主要用于頻繁使用的數據結構,如task_struct、mm_struct、vm_area_struct、 file、 dentry、 inode等
  • 在內核中初始化開銷不大的數據結構可以合用一個通用的緩沖區。通用緩沖區最小的為32字節,然后依次為64、128、…直至128K(即32個頁面),但是,對通用緩沖區的管理又采用的是Slab方式。

2.6 交換機制

當物理內存出現不足時,Linux 內存管理子系統需要釋放部分物理內存頁面。這一任務由內核的交換守護進程 kswapd 完成,該內核守護進程實際是一個內核線程,它在內核初始化時啟動,并周期地運行。它的任務就是保證系統中具有足夠的空閑頁面,從而使內存管理子系統能夠有效運行。
在Linux中,交換的單位是頁面而不是進程。盡管交換的單位是頁面,但交換還是要付出一定的代價,尤其是時間的代價。實際上,在操作系統中,時間和空間是一對矛盾,常常需要在二者之間作出平衡,有時需要以空間換時間,有時需要以時間換空間,頁面交換就是典型的以時間換空間。這里要說明的是,頁面交換是不得已而為之,例如在時間要求比較緊急的實時系統中,是不宜采用頁面交換機制的,因為它使程序的執行在時間上有了較大的不確定性。因此,Linux給用戶提供了一種選擇,可以通過命令或系統調用開啟或關閉交換機制。
??在頁面交換中,頁面置換算法是影響交換性能的關鍵性指標,其復雜性主要與換出有關。具體說來,必須考慮三個主要問題:

  1. 哪種頁面要換出
  2. 如何在交換區中存放頁面
  3. 如何選擇被交換出的頁面

請注意,我們在這里所提到的頁或頁面指的是其中存放的數據,因此,所謂頁面的換入換出實際上是指頁面中數據的換入換出。

2.6.1 哪種頁面被換出

交換的最終目的是頁面的回收,只有與用戶空間建立了映射關系的物理頁面才會被換出去,而內核空間中內核所占的頁面則常駐內存。可以把用戶空間中的頁面按其內容和性質分為以下幾種:

  1. 進程映像所占的頁面,包括進程的代碼段、數據段、堆棧段以及動態分配的“存儲堆”
  2. 通過系統調用mmap()把文件的內容映射到用戶空間
  3. 進程間共享內存區

對于第1種情況,進程的代碼段數據段所占的內存頁面可以被換入換出,但堆棧所占的頁面一般不被換出,因為這樣可以簡化內核的設計。
對于第2種情況,這些頁面所使用的交換區就是被映射的文件本身。
對于第3種情況,其頁面的換入換出比較復雜。
與此相對照,映射到內核空間中的頁面都不會被換出。

2.6.2 如何在交換區中存放頁面

我們知道物理內存被劃分為若干頁面,每個頁面的大小為4KB。實際上,交換區也被劃分為塊,每個塊的大小正好等于一頁,我們把交換區中的一塊叫做一個頁插槽(page slot),意思是說,把一個物理頁面插入到一個插槽中。當進行換出時,內核盡可能把換出的頁放在相鄰的插槽中,從而減少在訪問交換區時磁盤的尋道時間。

2.6.3 如何選擇被交換出的頁面

頁面交換是非常復雜的,其主要內容之一就是如何選擇要換出的頁面,我們以循序漸進的方式來討論頁面交換策略的選擇。
??策略一,需要時才交換。每當缺頁異常發生時,就給它分配一個物理頁面。如果發現沒有空閑的頁面可供分配,就設法將一個或多個內存頁面換出到磁盤上,從而騰出一些內存頁面來。這種交換策略確實簡單,但有一個明顯的缺點,這是一種被動的交換策略,需要時才交換,系統勢必要付出相當多的時間進行換入換出。
??策略二,系統空閑時交換。與策略一相比較,這是一種積極的交換策略,也就是,在系統空閑時,預先換出一些頁面而騰出一些內存頁面,從而在內存中維持一定的空閑頁面供應量,使得在缺頁中斷發生時總有空閑頁面可供使用。至于換出頁面的選擇,一般都采用LRU算法。但是這種策略實施起來也有困難,因為并沒有哪種方法能準確地預測對頁面的訪問,所以,完全可能發生這樣的情況,即一個好久沒有受到訪問的頁面剛剛被換出去,卻又要訪問它了,于是又把它換進來。在最壞的情況下,有可能整個系統的處理能力都被這樣的換入/換出所影響,而根本不能進行有效的計算和操作。這種現象被稱為頁面的“抖動”。
??策略三,換出但并不立即釋放。當系統挑選出若干頁面進行換出時,將相應的頁面寫入磁盤交換區中,并修改相應頁表中頁表項的內容(把present標志位置為0),但是并不立即釋放,而是將其page結構留在一個緩沖(cache)隊列中,使其從活躍(active)狀態轉為不活躍(Inactive)狀態。至于這些頁面的最后釋放,要推遲到必要時才進行。這樣,如果一個頁面在釋放后又立即受到訪問,就可以從物理頁面的緩沖隊列中找到相應的頁面,再次為之建立映射。由于此頁面尚未釋放,還保留著原來的內容,就不需要磁盤讀入了。經過一段時間以后,一個不活躍的內存頁面一直沒有受到訪問,那這個頁面就需要真正被釋放了。
??策略四,把頁面換出推遲到不能再推遲為止。實際上,策略三還有值得改進的地方。首先在換出頁面時不一定要把它的內容寫入磁盤。如果一個頁面自從最近一次換入后并沒有被寫過(如代碼),那么這個頁面是“干凈的”,就沒有必要把它寫入磁盤。其次,即使“臟”頁面,也沒有必要立即寫出去,可以采用策略三。至于“干凈”頁面,可以一直緩沖到必要時才加以回收,因為回收一個“干凈”頁面花費的代價很小。

2.6.4 頁面交換守護進程kswapd

為了避免在CPU忙碌的時候,也就是在缺頁異常發生時,臨時搜索可供換出的內存頁面并加以換出,Linux內核定期地檢查系統內的空閑頁面數是否小于預定義的極限,一旦發現空閑頁面數太少,就預先將若干頁面換出,以減輕缺頁異常發生時系統所承受的負擔。
??Kswapd的執行路線分為兩部分,第一部分是發現物理頁面已經短缺的情況下才進行的,目的在于預先找出若干頁面,且將這些頁面的映射斷開,使這些物理頁面從活躍狀態轉入不活躍狀態,為頁面的換出做好準備。第二部分是每次都要執行的,目的在于把已經處于不活躍狀態的“臟” 頁面寫入交換區,使他們成為不活躍的“干凈”頁面繼續緩沖,或進一步回收這樣的頁面成為空閑頁面。

3 其他

3.1 Linux內存相關命令

3.1.1 free

free 執行命令顯示結果如下:

             total       used       free     shared    buffers     cached
Mem:      16470320   15687728     782592          0     220368   13893088
-/+ buffers/cache:    1574272   14896048
Swap:     16777208     295956   16481252

具體含義如下:

total used free shared buffers cached
Mem 總物理內存 當前使用的內存(包括slab+buffers+cached) 完全沒有使用的內存 進程間共享的內存 緩存文件的元數據 緩存文件的具體內容
-/+ buffers/cache 當前使用的內存(不包括buffers+cached,但包括slab) 未使用和緩存的內存(free+buffers+cached)
swap 總的交換空間 已使用的交換空間 未使用的交換空間

系統實際可以使用的內存之和是:

avaiable_phisical_memory = free + buffers + cached + slab
  • buffer :作為buffer cache的內存,是塊設備的讀寫緩沖區,在沒有文件系統的情況下,直接對磁盤進行操作的數據會緩存到buffer cache中,例如,文件系統的元數據都會緩存到buffer cache中。
  • cache:作為page cache的內存,是文件的緩存,在文件層面上的數據會緩存到page cache。文件的邏輯層需要映射到實際的物理磁盤,這種映射關系由文件系統來完成。
  • slab: 內核緩存,用戶快速創建內核中的對象所用。

如果 cache 的值很大,說明cache住的文件數很多。如果頻繁訪問到的文件都能被cache住,那么磁盤的讀IO 必會非常小。

可以使用下面命令主動釋放 cache占用的內存

echo 1 > /proc/sys/vm/drop_caches

可以使用下面命令主動釋放flush buffer中的內容。

sync

可以使用下面命令主動釋放 slab占用的內存

echo 2 > /proc/sys/vm/drop_caches

3.1.2 /proc/meminfo

/proc/meminfo是了解Linux系統內存使用狀況的主要接口,我們最常用的”free”、”vmstat”等命令就是通過它獲取數據的 ,/proc/meminfo所包含的信息比”free”等命令要豐富得多。
cat /proc/meminfo ,輸出內容如下:

MemTotal: 510080 kB(總的內存)
MemFree: 17924 kB(未使用的內存)
Buffers: 4644 kB(用來給文件做緩沖內存)
Cached: 35104 kB(被高速緩沖存儲器(cache memory)用的內存的大小)
SwapCached: 4540 kB(交換空間)
Active: 330988 kB(活躍使用中的緩沖或高速緩沖存儲器頁面文件的大小)
Inactive: 137500 kB(不經常使用中的緩沖或高速緩沖存儲器頁面文件的大小,可能被用于其他途徑)
Active(anon): 318148 kB
Inactive(anon): 111000 kB
Active(file): 12840 kB
Inactive(file): 26500 kB
Unevictable: 0 kB
Mlocked: 0 kB
HighTotal: 0 kB
HighFree: 0 kB
LowTotal: 510080 kB
LowFree: 17924 kB
SwapTotal: 1048568 kB
SwapFree: 1040780 kB
Dirty: 0 kB
Writeback: 4 kB
AnonPages: 424508 kB
Mapped: 14052 kB
Shmem: 420 kB
Slab: 12460 kB
SReclaimable: 4536 kB
SUnreclaim: 7924 kB
KernelStack: 1296 kB
PageTables: 4720 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 1303608 kB
Committed_AS: 1058568 kB
VmallocTotal: 505848 kB
VmallocUsed: 1236 kB
VmallocChunk: 503196 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 6144 kB
DirectMap2M: 518144 kB

3.1.3 vmstat

vmstat是一個查看虛擬內存(Virtual Memory)使用狀況的工具。用法如下描述:

vmstat [-a] [-n] [-S unit] [delay [ count]]
vmstat [-s] [-n] [-S unit]
vmstat [-m] [-n] [delay [ count]]
vmstat [-d] [-n] [delay [ count]]
vmstat [-p disk partition] [-n] [delay [ count]]
vmstat [-f]
vmstat [-V]

-a:顯示活躍和非活躍內存
-f:顯示從系統啟動至今的fork數量 。
-m:顯示slabinfo
-n:只在開始時顯示一次各字段名稱。
-s:顯示內存相關統計信息及多種系統活動數量。
delay:刷新時間間隔。如果不指定,只顯示一條結果。
count:刷新次數。如果不指定刷新次數,但指定了刷新時間間隔,這時刷新次數為無窮。
-d:顯示磁盤相關統計信息。
-p:顯示指定磁盤分區統計信息
-S:使用指定單位顯示。參數有 k 、K 、m 、M ,分別代表1000、1024、1000000、1048576字節(byte)。默認單位為K(1024 bytes)
-V:顯示vmstat版本信息。

下圖顯示一個用法實例:

vmstat -3 結果

字段說明:

Procs
       r: The number of processes waiting for run time.
       b: The number of processes in uninterruptible sleep.


   Memory
       swpd: the amount of virtual memory used.
       free: the amount of idle memory.
       buff: the amount of memory used as buffers.
       cache: the amount of memory used as cache.
       inact: the amount of inactive memory. (-a option)
       active: the amount of active memory. (-a option)


   Swap
       si: Amount of memory swapped in from disk (/s).
       so: Amount of memory swapped to disk (/s).


   IO
       bi: Blocks received from a block device (blocks/s).
       bo: Blocks sent to a block device (blocks/s).


   System
       in: The number of interrupts per second, including the clock.
       cs: The number of context switches per second.

   CPU
       These are percentages of total CPU time.
       us: Time spent running non-kernel code. (user time, including nice time)
       sy: Time spent running kernel code. (system time)
       id: Time spent idle. Prior to Linux 2.5.41, this includes IO-wait time.
       wa: Time spent waiting for IO. Prior to Linux 2.5.41, shown as zero.

3.1.4 top

top命令是Linux下常用的性能分析工具,能夠實時顯示系統中各個進程的資源占用狀況,包括整體和單個進程。
top命令執行結果示例如下:

top命令結果

第一行是任務隊列信息,同 uptime 命令的執行結果。其內容如下:

字段 說明
09:44:34 當前時間
up 58 days,22:26 系統運行時間
1 user 當前登錄用戶數
load average: 2.02, 2.07, 2.04 系統負載,即任務隊列的平均長度。三個數值分別為 1分鐘、5分鐘、15分鐘前到現在的平均值。

第二、三行為進程和CPU的信息

字段 說明
Tasks: 202 total 進程總數量
1 running 正在運行的進程數
201 sleeping 休眠的進程數
0 stopped 停止的進程數
0 zombie 僵尸進程數
Cps(s): 3.4% us 用戶空間占用CPU百分比
2.5% sy 內核空間占用cpu百分比
0.6% ni 用戶進程空間內改變過優先級的進程占用CPU百分比第二、三行為進程和CPU的信息
93.5 % id 空閑CPU百分比
0.0% wa 等待輸入輸出的CPU時間百分比
0.0% hi 硬件CPU中斷占用百分比
0.1% si 軟中斷占用百分比

最后兩行為內存信息,和free類似,不在做說明。

統計信息區域的下方顯示了各個進程的詳細信息

序號  列名    含義
a    PID     進程id
b    PPID    父進程id
c    RUSER   Real user name
d    UID     進程所有者的用戶id
e    USER    進程所有者的用戶名
f    GROUP   進程所有者的組名
g    TTY     啟動進程的終端名。不是從終端啟動的進程則顯示為 ?
h    PR      優先級
i    NI      nice值。負值表示高優先級,正值表示低優先級
j    P       最后使用的CPU,僅在多CPU環境下有意義
k    %CPU    上次更新到現在的CPU時間占用百分比
l    TIME    進程使用的CPU時間總計,單位秒
m    TIME+   進程使用的CPU時間總計,單位1/100秒
n    %MEM    進程使用的物理內存百分比
o    VIRT    進程使用的虛擬內存總量,單位kb。VIRT=SWAP+RES
p    SWAP    進程使用的虛擬內存中,被換出的大小,單位kb。
q    RES     進程使用的、未被換出的物理內存大小,單位kb。RES=CODE+DATA
r    CODE    可執行代碼占用的物理內存大小,單位kb
s    DATA    可執行代碼以外的部分(數據段+棧)占用的物理內存大小,單位kb
t    SHR     共享內存大小,單位kb
u    nFLT    頁面錯誤次數
v    nDRT    最后一次寫入到現在,被修改過的頁面數。
w    S       進程狀態(D=不可中斷的睡眠狀態,R=運行,S=睡眠,T=跟蹤/停止,Z=僵尸進程)
x    COMMAND 命令名/命令行
y    WCHAN   若該進程在睡眠,則顯示睡眠中的系統函數名
z    Flags   任務標志,參考 sched.h

附常用操作:

top   //每隔5秒顯式所有進程的資源占用情況
top -d 2  //每隔2秒顯式所有進程的資源占用情況
top -c  //每隔5秒顯式進程的資源占用情況,并顯示進程的命令行參數(默認只有進程名)
top -p 12345 -p 6789//每隔5秒顯示pid是12345和pid是6789的兩個進程的資源占用情況
top -d 2 -c -p 123456 //每隔2秒顯示pid是12345的進程的資源使用情況,并顯式該進程啟動的命令行參數

3.1.5 pmap

pmap 用于報道進程的內存映射關系。
用法

pmap PID
或者
pmap [options] PID
在輸出中它顯示全部的地址,kbytes,mode還有mapping。
選項
-x extended顯示擴展格式
-d device顯示設備格式
-q quiet不顯示header/footer行
-V 顯示版本信息

擴展和設備格式區域

Address: 內存開始地址
Kbytes: 占用內存的字節數(KB)
RSS: 保留內存的字節數(KB)
Dirty: 臟頁的字節數(包括共享和私有的)(KB)
Mode: 內存的權限:read、write、execute、shared、private (寫時復制)
Mapping: 占用內存的文件、或[anon](分配的內存)、或[stack](堆棧)
Offset: 文件偏移
Device: 設備名 (major:minor)

3.1.6 ps

名稱:ps
使用權限:所有使用者
使用方式:ps [options] [--help]
說明:顯示瞬間行程 (process) 的動態
參數:ps的參數非常多, 在此僅列出幾個常用的參數并大略介紹含義
-A    列出所有的進程
-w    顯示加寬可以顯示較多的資訊
-au    顯示較詳細的資訊
-aux    顯示所有包含其他使用者的行程



常用參數:
-A 顯示所有進程(等價于-e)(utility)
-a 顯示一個終端的所有進程,除了會話引線
-N 忽略選擇。
-d 顯示所有進程,但省略所有的會話引線(utility)
-x 顯示沒有控制終端的進程,同時顯示各個命令的具體路徑。dx不可合用。(utility)
-p pid 進程使用cpu的時間
-u uid or username 選擇有效的用戶id或者是用戶名
-g gid or groupname 顯示組的所有進程。
U username 顯示該用戶下的所有進程,且顯示各個命令的詳細路徑。如:ps U zhang;(utility)
-f 全部列出,通常和其他選項聯用。如:ps -fa or ps -fx and so on.
-l 長格式(有F,wchan,C 等字段)
-j 作業格式
-o 用戶自定義格式。
v 以虛擬存儲器格式顯示
s 以信號格式顯示
-m 顯示所有的線程
-H 顯示進程的層次(和其它的命令合用,如:ps -Ha)(utility)
e 命令之后顯示環境(如:ps -d e; ps -a e)(utility)
h 不顯示第一行

ps aux 輸出格式如下:

ps aux 輸出

ps -T -p pid 可以顯示進程pid下面說有的線程列表, 如下范例:

ps -T -p pid

3.2 overcommit

Memory Overcommit的意思是操作系統承諾給進程的內存大小超過了實際可用的內存。一個保守的操作系統不會允許memory overcommit,有多少就分配多少,再申請就沒有了,這其實有些浪費內存,因為進程實際使用到的內存往往比申請的內存要少,比如某個進程malloc()了200MB內存,但實際上只用到了100MB,按照UNIX/Linux的算法,物理內存頁的分配發生在使用的瞬間,而不是在申請的瞬間,也就是說未用到的100MB內存根本就沒有分配,這100MB內存就閑置了。下面這個概念很重要,是理解memory overcommit的關鍵:commit(或overcommit)針對的是內存申請,內存申請不等于內存分配,內存只在實際用到的時候才分配。
??Linux是允許memory overcommit的,只要你來申請內存我就給你,寄希望于進程實際上用不到那么多內存,但萬一用到那么多了呢?那就會發生類似“銀行擠兌”的危機,現金(內存)不足了。Linux設計了一個OOM killer機制(OOM = out-of-memory)來處理這種危機:挑選一個進程出來殺死,以騰出部分內存,如果還不夠就繼續殺…也可通過設置內核參數 vm.panic_on_oom 使得發生OOM時自動重啟系統。
內核參數 vm.overcommit_memory 接受三種取值

  • 0 – Heuristic overcommit handling. 這是缺省值,它允許overcommit,但過于明目張膽的overcommit會被拒絕,比如malloc一次性申請的內存大小就超過了系統總內存。Heuristic的意思是“試探式的”,內核利用某種算法(對該算法的詳細解釋請看文末)猜測你的內存申請是否合理,它認為不合理就會拒絕overcommit。
  • 1 – Always overcommit. 允許overcommit,對內存申請來者不拒。
  • 2 – Don’t overcommit. 禁止overcommit。

系統具體使用配置的值可以通過這個文件 /proc/sys/vm/overcommit_memory查看

內核參數vm.overcommit_ratio 只有當vm.overcommit_memory = 2的時候才會生效,內存可申請內存為

SWAP內存大小 + 物理內存 * overcommit_ratio/100

查看系統overcommit信息

 grep -i commit /proc/meminfo
CommitLimit:    517584 kB
Committed_AS:  3306488 kB

CommitLimit:最大能分配的內存(僅在vm.overcommit_memory=2時候生效),具體的值是
SWAP內存大小 + 物理內存 * overcommit_ratio / 100

Committed_AS:當前已經分配的內存大小

4 參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容