1 內(nèi)存尋址
1.1 物理地址、虛擬地址以及線性地址
1.2 段機(jī)制
1.3 linux中的段機(jī)制
1.4 分頁機(jī)制
1.4.1 為什么使用兩級(jí)分頁
1.4.2 兩級(jí)頁表結(jié)構(gòu)
1.4.3 頁面高速緩存
1.5 linux分頁機(jī)制
2 內(nèi)存管理
2.1 虛擬內(nèi)存、內(nèi)核空間和用戶空間
2.2 虛擬內(nèi)存實(shí)現(xiàn)機(jī)制之間的關(guān)系
2.3 進(jìn)程用戶空間管理
2.3.1 進(jìn)程用戶空間簡述
2.3.2 進(jìn)程用戶空間創(chuàng)建
2.3.3 虛存映射
2.4 請(qǐng)頁機(jī)制
2.4.1 請(qǐng)求調(diào)頁
2.4.2 寫時(shí)復(fù)制
2.5 物理頁的分配和回收
2.5.1 伙伴算法
2.5.2 slab機(jī)制分配
2.6 交換機(jī)制
2.6.1 哪種頁面被換出
2.6.2 如何在交換區(qū)中存放頁面
2.6.3 如何選擇被交換出的頁面
2.6.4 頁面交換守護(hù)進(jìn)程kswapd
3 其他
3.1 Linux內(nèi)存相關(guān)命令
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 內(nèi)存尋址
1.1 物理地址、虛擬地址以及線性地址
- 物理地址: 物理內(nèi)存的內(nèi)存單元地址
- 虛擬地址: 程序員看到的內(nèi)存空間定義未虛擬地址,intel X86 CPU尋址使用了段機(jī)制,最初的8086中有4個(gè)16位的段寄存器:CS、DS、SS、ES,分別用于存放可執(zhí)行代碼的代碼段、數(shù)據(jù)段、堆棧段和其他段的基地址,解決了CPU數(shù)據(jù)總線16位尋址20位數(shù)據(jù)地址空間的問題。 虛擬地址一般用“段:偏移量”的形式來描述,比如在8086中A815:CF2D就代表段首地址為A815,段內(nèi)偏移位為CF2D的虛地址。
- 線性地址: 是指一段連續(xù)的,不分段的,范圍為0到4GB的地址空間,一個(gè)線性地址就是線性地址空間的一個(gè)絕對(duì)地址。
尋址模式有2種:
實(shí)模式: 是 段地址+偏移量 的方式,得到物理地址;如當(dāng)程序執(zhí)行“mov ax,[1024]”這樣一條指令時(shí),在8086的實(shí)模式下,把某一段寄存器(比如ds)左移4位,然后與16位的偏移量(1024)相加后被直接送到內(nèi)存總線上,這個(gè)相加后的地址就是內(nèi)存單元的物理地址,而程序中的地址(例如ds:1024)就叫虛擬地址
-
保護(hù)模式:不 允許通過段寄存器取值得到段的起始地址,而是把虛擬地址轉(zhuǎn)進(jìn)一個(gè) MMU 的硬件,經(jīng)過額外的轉(zhuǎn)換和檢查,進(jìn)而得到一個(gè)物理地址,如下圖所示:
保護(hù)模式下尋址
MMU是一種硬件電路,它包含兩個(gè)部件,一個(gè)是分段部件,一個(gè)是分頁部件,支持分段機(jī)制和分頁機(jī)制。分段機(jī)制把一個(gè)虛擬地址轉(zhuǎn)換為線性地址;接著,分頁機(jī)制把一個(gè)線性地址轉(zhuǎn)換為物理地址,如下圖所示:

1.2 段機(jī)制
段是虛擬地址空間的基本單位,段機(jī)制將虛擬地址空間地址轉(zhuǎn)換為線性地址空間的一個(gè)線性地址。
段描述符包含下面的信息:
- 段的基地址:在線性空間段中的起始地址;
- 段的界限:在虛擬地址空間內(nèi),段內(nèi)可以使用的最大偏移量;
- 段的保護(hù)屬性:如段是否可以被寫入或者讀出、段的特權(quán)級(jí)別等等
下圖說明了虛擬地址空間和線性地址空間的映射關(guān)系:

在虛擬地址轉(zhuǎn)換為線性地址過程中,會(huì)做如下檢查:
- 段內(nèi)偏移如果大于段界限,系統(tǒng)講產(chǎn)生異常;
- 如果對(duì)一個(gè)段進(jìn)行訪問,系統(tǒng)會(huì)根據(jù)段的保護(hù)屬性檢查訪問者是否具有訪問權(quán)限,如果沒有,則產(chǎn)生異常。
1.3 linux中的段機(jī)制
intel段機(jī)制是從8086引入,最初是為了解決CPU內(nèi)部16位地址到20位實(shí)地址的轉(zhuǎn)換。為了保持兼容性,386仍然使用了段機(jī)制。linux目前所有的進(jìn)程都使用了相同的邏輯空間地址,由于絕大多數(shù)硬件平臺(tái)都不支持段機(jī)制,只支持分頁機(jī)制,所以為了讓Linux具有更好的可移植性,linux需要去掉段機(jī)制而只使用分頁機(jī)制。 然后80x86系列CPU必須使用段機(jī)制,不能繞過段機(jī)制直接給出線性地址空間地址,linux上讓段的基地址為0,在32位系統(tǒng)下段界限為4G, 使用這種巧妙的訪問繞過了段機(jī)制。
另外80X86要求必須為數(shù)據(jù)段和代碼段創(chuàng)建不同的段,不僅如此,linux內(nèi)核運(yùn)行在特權(quán)級(jí)別0,和用戶程序運(yùn)行在特權(quán)級(jí)別3,根據(jù)80x86的段保護(hù)機(jī)制,特權(quán)級(jí)3的程序無法訪問特權(quán)級(jí)別0的段,所以linux必須為內(nèi)核和用戶程序分別創(chuàng)建獨(dú)立的數(shù)據(jù)段和代碼段;linux內(nèi)核不區(qū)分?jǐn)?shù)據(jù)段和堆棧段,同時(shí)也只使用了段的2個(gè)保護(hù)級(jí)別,簡化的段底層復(fù)雜的設(shè)計(jì)。
linux這樣使用段機(jī)制違背了段最初的設(shè)計(jì)初衷:不同的段映射到不用的線性地址空間中。 linux上段使用了完全相同線性地址空間,他們可以相互覆蓋,這樣線性地址空間映射到物理地址空間,修改任何一個(gè)段的數(shù)據(jù)都會(huì)影響其他段。linux首先利用的段的特權(quán)級(jí)別保護(hù)內(nèi)核段不會(huì)被用戶程序訪問和修改,其次引入了分頁機(jī)制保護(hù)了段數(shù)據(jù)。
1.4 分頁機(jī)制
分頁機(jī)制再分段機(jī)制之后進(jìn)行,完成線性地址到物理地址轉(zhuǎn)換的過程。如果不允許分頁,段機(jī)制轉(zhuǎn)換的線性地址就是物理地址;如果允許分頁機(jī)制,那么線性地址就需要通過分頁機(jī)制找到物理地址。
線性地址空間被劃分為若干塊大小相等的片,稱為頁,并把各頁編號(hào),同樣的,物理地址也被劃分為若干塊大小相等的片。線性地址頁和物理頁有映射關(guān)系,如下圖所示:

那么,頁的大小應(yīng)該為多少?頁過大或過小都會(huì)影響內(nèi)存的使用率。其大小在設(shè)計(jì)硬件分頁機(jī)制時(shí)就必須確定下來,例如80X86支持的標(biāo)準(zhǔn)頁大小為4KB(也支持4MB)。
1.4.1 為什么使用兩級(jí)分頁
假設(shè)每個(gè)進(jìn)程都需要占用4GB的線性地址空間,那么就需要1M個(gè)頁表,每個(gè)頁表需要4字節(jié)的描述信息,這樣每個(gè)進(jìn)程頁表就得占用4M的空間,為了減少頁表空間占用,使用了兩級(jí)分頁機(jī)制,每個(gè)進(jìn)程都被分配一個(gè)頁目錄,只有被使用到頁表才會(huì)分配到內(nèi)存中。一級(jí)頁表需要一次分配所有頁表空間,兩級(jí)頁表則可以在需要的時(shí)候再分配頁表空間。
1.4.2 兩級(jí)頁表結(jié)構(gòu)
兩級(jí)頁表第一級(jí)成為頁目錄,存儲(chǔ)在一個(gè)4K字節(jié)的頁中。頁目錄有1K個(gè)表項(xiàng),每個(gè)表項(xiàng)4個(gè)字節(jié),并指向第二級(jí)表。線性地址包含一級(jí)索引、二級(jí)索引和偏移。 一級(jí)索引占線性地址的高10位,指向1k個(gè)頁目錄,二級(jí)索引占用中間10位,指向了物理地址的頁表項(xiàng),最后12位指向了物理頁的偏移,如下圖描述:

其中,寄存器CR3中存儲(chǔ)了頁目錄的起始地址。
這里比較巧妙的地方是頁都是4K的整數(shù)倍,所以低12位都是0, 利用這低12位可以存儲(chǔ)頁面的屬性信息,如下所述:
- 第0位是存在位,如果P=1,表示頁表地址指向的該頁在內(nèi)存中,如果P=0,表示不在內(nèi)存中。
- 第1位是讀/寫位,第2位是用戶/管理員位,這兩位為頁目錄項(xiàng)提供硬件保護(hù)。當(dāng)特權(quán)級(jí)為3的進(jìn)程要想訪問頁面時(shí),需要通過頁保護(hù)檢查,而特權(quán)級(jí)為0的進(jìn)程就可以繞過頁保護(hù)。
- 第3位是PWT(Page Write-Through)位,表示是否采用寫透方式,寫透方式就是既寫內(nèi)存(RAM)也寫高速緩存,該位為1表示采用寫透方式
- 第4位是PCD(Page Cache Disable)位,表示是否啟用高速緩存,該位為1表示啟用高速緩存。
- 第5位是訪問位,當(dāng)對(duì)頁目錄項(xiàng)進(jìn)行訪問時(shí),A位=1。
- 第7位是Page Size標(biāo)志,只適用于頁目錄項(xiàng)。如果置為1,頁目錄項(xiàng)指的是4MB的頁面。
- 第9~11位由操作系統(tǒng)專用,Linux也沒有做特殊之用。
1.4.3 頁面高速緩存
兩級(jí)分頁信息都是存儲(chǔ)再內(nèi)存中的,這樣CPU每取一個(gè)物理數(shù)據(jù),都必須經(jīng)過至少2次內(nèi)存訪問,大大降低的訪問速度。為了提高速度,在80X86中設(shè)置一個(gè)最近存取頁的高速緩存硬件機(jī)制,它自動(dòng)保持32項(xiàng)處理器最近使用的頁表項(xiàng),因此,可以覆蓋128K字節(jié)的內(nèi)存地址。當(dāng)訪問線性地址空間的某個(gè)地址時(shí),先檢查對(duì)應(yīng)的頁表項(xiàng)是否在高速緩存中,如果在,就不必經(jīng)過兩級(jí)訪問了,如果不在,再進(jìn)行兩級(jí)訪問。平均來說,頁面高速緩存大約有90%的命中率,也就是說每次訪問存儲(chǔ)器時(shí),只有10%的情況必須訪問兩級(jí)分頁機(jī)構(gòu)。過程如下下圖所示:

1.5 linux分頁機(jī)制
為了兼容32和64位系統(tǒng),linux提供了3級(jí)分頁機(jī)制:
- 頁總目錄PGD(Page Global Directory)
- 頁中間目錄PMD(Page Middle Directory)
- 頁表PT(Page Table)
具體尋找邏輯圖如下:

??盡管Linux采用的是三級(jí)分頁模式,但我們的討論還是以80X86的兩級(jí)分頁模式為主,因此,Linux忽略中間目錄層,以后,我們把頁總目錄就叫頁目錄。
??每一個(gè)進(jìn)程有它自己的頁目錄和自己的頁表集。當(dāng)進(jìn)程切換發(fā)生時(shí),Linux把cr3控制寄存器的內(nèi)容保存在前一個(gè)執(zhí)行進(jìn)程的PCB中,然后把下一個(gè)要執(zhí)行進(jìn)程的PCB的值裝入cr3寄存器中。因此,當(dāng)新進(jìn)程恢復(fù)在CPU上執(zhí)行時(shí),分頁單元指向一組正確的頁表。
2 內(nèi)存管理
2.1 虛擬內(nèi)存、內(nèi)核空間和用戶空間
linux簡化了分段機(jī)制,使得虛擬地址和線性地址一致,線性地址空間在32位系統(tǒng)上固定為4GB大小,linux的虛擬地址空間也這么大大,啟動(dòng)最高1G因?yàn)閮?nèi)核空間,剩余的3G為用戶空間,內(nèi)核空間為共享,每個(gè)進(jìn)程都可以通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核空間。對(duì)于具體每個(gè)進(jìn)程來說都擁有4GB的虛擬空間。下圖給出進(jìn)程虛擬空間示意圖:

??用戶空間是進(jìn)程隔離的,不用用戶空間上相同的虛擬地址實(shí)際物理內(nèi)存是不一樣的,一個(gè)CPU同一時(shí)刻只會(huì)執(zhí)行一個(gè)進(jìn)程,在CPU眼中,只存在一個(gè)虛擬地址空間,進(jìn)程切換的時(shí)候?qū)儆谶M(jìn)程的頁表也會(huì)發(fā)生變化,由于虛擬地址和物理地址映射主要是頁表機(jī)制實(shí)現(xiàn)的,頁表的變化也意味著物理地址空間的變化。
??linux內(nèi)核雖然占據(jù)了虛擬地址空間的高位,但是映射到物理內(nèi)存地址空間卻是從最低位開始的,示意圖如下所示:

2.2 虛擬內(nèi)存實(shí)現(xiàn)機(jī)制之間的關(guān)系
linux虛擬內(nèi)存實(shí)現(xiàn)需要多種機(jī)制支持,核心機(jī)制如下:
- 地址映射機(jī)制
- 請(qǐng)頁機(jī)制
- 內(nèi)存回收和分配機(jī)制
- 交換機(jī)制
- 緩存和刷新機(jī)制
這幾種機(jī)制關(guān)系如下圖所示:

首先內(nèi)核通過地址映射機(jī)制將進(jìn)程虛擬地址空間映射到物理地址空間,當(dāng)進(jìn)程運(yùn)行時(shí),如果要使用某個(gè)頁但是這個(gè)頁沒有建立和物理內(nèi)存頁的關(guān)系,就需要請(qǐng)頁;如果有空閑內(nèi)存使用,就會(huì)進(jìn)行內(nèi)存分配和回收,并且進(jìn)行講物理頁緩存起來;如果沒有足夠的內(nèi)存使用,就使用交換機(jī)制,騰出一部分內(nèi)存;另外在地址映射中要通過TLB來尋找物理頁;交換機(jī)制中也要用到交換緩存,并且把物理頁內(nèi)容交換到交換文件中后也要修改頁表來映射文件地址。
2.3 進(jìn)程用戶空間管理
2.3.1 進(jìn)程用戶空間簡述
下圖是一個(gè)進(jìn)程用戶空間的簡單劃分:

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

??內(nèi)核內(nèi)部關(guān)于進(jìn)程空間管理數(shù)據(jù)結(jié)果的關(guān)系如下圖所示:

2.3.2 進(jìn)程用戶空間創(chuàng)建
進(jìn)程的用戶空間是在執(zhí)行系統(tǒng)調(diào)用的fork時(shí)創(chuàng)建的,基于寫時(shí)復(fù)制的原理,子進(jìn)程創(chuàng)建的時(shí)候繼承了父進(jìn)程的用戶空間,僅僅是mm_struc結(jié)構(gòu)的建立、vm_area_struct結(jié)構(gòu)的建立以及頁目錄和頁表的建立,并沒有真正地復(fù)制一個(gè)物理頁面,這也是為什么Linux內(nèi)核能迅速地創(chuàng)建進(jìn)程的原因之一。
2.3.3 虛存映射
當(dāng)調(diào)用exec()系統(tǒng)調(diào)用開始執(zhí)行一個(gè)進(jìn)程時(shí),進(jìn)程的可執(zhí)行映像(包括代碼段、數(shù)據(jù)段等)必須裝入到進(jìn)程的用戶地址空間。如果該進(jìn)程用到了任何一個(gè)共享庫,則共享庫也必須裝入到進(jìn)程的用戶空間。Linux并不將映像裝入到物理內(nèi)存,相反,可執(zhí)行文件只是被連接到進(jìn)程的用戶空間中。隨著進(jìn)程的運(yùn)行,被引用的程序部分會(huì)由操作系統(tǒng)裝入到物理內(nèi)存,這種將映像鏈接到進(jìn)程用戶空間的方法被稱為“虛存映射”,也就是把文件從磁盤映射到進(jìn)程的用戶空間,這樣把對(duì)文件的訪問轉(zhuǎn)化為對(duì)虛存區(qū)的訪問。有兩種類型的虛存映射:
- 共享的:有幾個(gè)進(jìn)程共享這一映射,也就是說,如果一個(gè)進(jìn)程對(duì)共享的虛存區(qū)進(jìn)行寫,其它進(jìn)程都能感覺到,而且會(huì)修改磁盤上對(duì)應(yīng)的文件。
- 私有的:進(jìn)程創(chuàng)建的這種映射只是為了讀文件,而不是寫文件,因此,對(duì)虛存區(qū)的寫操作不會(huì)修改磁盤上的文件,由此可以看出,私有映射的效率要比共享映射的高。
除了這兩種映射外,如果映射與文件無關(guān),就叫匿名映射。
當(dāng)某個(gè)可執(zhí)行映像映射到進(jìn)程用戶空間中并開始執(zhí)行時(shí),因?yàn)橹挥泻苌僖徊糠痔摯骓撁嫜b入到了物理內(nèi)存,可能會(huì)遇到所訪問的數(shù)據(jù)不在物理內(nèi)存。這時(shí),處理器將向Linux 報(bào)告一個(gè)頁故障及其對(duì)應(yīng)的故障原因,于是就用到了后面講述的請(qǐng)頁機(jī)制。
2.4 請(qǐng)頁機(jī)制
當(dāng)一個(gè)進(jìn)程執(zhí)行時(shí),如果CPU訪問到一個(gè)有效的虛地址,但是這個(gè)地址對(duì)應(yīng)的頁沒有在內(nèi)存,則CPU產(chǎn)生一個(gè)缺頁異常。如果這個(gè)虛存區(qū)的訪問權(quán)限與引起缺頁異常的訪問類型相匹配,則調(diào)用handle_mm_fault()函數(shù),該函數(shù)確定如何給進(jìn)程分配一個(gè)新的物理頁面:
- 如果被訪問的頁不在內(nèi)存,也就是說,這個(gè)頁還沒有被存放在任何一個(gè)物理頁面中,那么,內(nèi)核分配一個(gè)新的頁面并適當(dāng)?shù)爻跏蓟?;這種技術(shù)稱為“請(qǐng)求調(diào)頁”。
- 如果被訪問的頁在內(nèi)存但是被標(biāo)為只讀,也就是說,它已經(jīng)被存放在一個(gè)頁面中,那么,內(nèi)核分配一個(gè)新的頁面,并把舊頁面的數(shù)據(jù)拷貝到新頁面來初始化它;這種技術(shù)稱為“寫時(shí)復(fù)制”。
2.4.1 請(qǐng)求調(diào)頁
“請(qǐng)求調(diào)頁”指的是一種動(dòng)態(tài)內(nèi)存分配技術(shù),它把頁面的分配推遲到不能再推遲為止,也就是說,一直推遲到進(jìn)程要訪問的頁不在物理內(nèi)存時(shí)為止,由此引起一個(gè)缺頁異常。
??請(qǐng)求調(diào)頁技術(shù)的引入主要是因?yàn)檫M(jìn)程開始運(yùn)行時(shí)并不訪問其地址空間中的全部地址;事實(shí)上,有一部分地址也許進(jìn)程永遠(yuǎn)不使用。此外,程序的局部性原理保證了在程序執(zhí)行的每個(gè)階段,真正使用的進(jìn)程頁只有一小部分,因此臨時(shí)用不著的頁根本沒必要調(diào)入內(nèi)存。
??但是,系統(tǒng)為此也要付出額外的開銷,這是因?yàn)橛烧?qǐng)求調(diào)頁所引發(fā)的每個(gè)“缺頁”異常必須由內(nèi)核處理,這將浪費(fèi)CPU的周期。幸運(yùn)的是,局部性原理保證了一旦進(jìn)程開始在一組頁上運(yùn)行,在接下來相當(dāng)長的一段時(shí)間內(nèi)它會(huì)一直停留在這些頁上而不去訪問其它的頁:這樣我們就可以認(rèn)為“缺頁”異常是一種稀有事件。
2.4.2 寫時(shí)復(fù)制
寫時(shí)復(fù)制(Copy-on-write)是一種可以推遲甚至免除拷貝數(shù)據(jù)的技術(shù)。內(nèi)核此時(shí)并不復(fù)制整個(gè)進(jìn)程空間,而是讓父進(jìn)程和子進(jìn)程共享同一個(gè)拷貝。只有在需要寫入的時(shí)候,數(shù)據(jù)才會(huì)被復(fù)制,從而使各個(gè)進(jìn)程擁有各自的拷貝。也就是說,資源的復(fù)制只有在需要寫入的時(shí)候才進(jìn)行,在此之前,以只讀方式共享。這種技術(shù)使地址空間上的頁的拷貝被推遲到實(shí)際發(fā)生寫入的時(shí)候。有時(shí)共享頁根本不會(huì)被寫入,例如,fork()后立即調(diào)用exec(),就無需復(fù)制父進(jìn)程的頁了。fork()的實(shí)際開銷就是復(fù)制父進(jìn)程的頁表以及給子進(jìn)程創(chuàng)建唯一的PCB。這種優(yōu)化可以避免拷貝大量根本就不會(huì)使用的數(shù)據(jù)
2.5 物理頁的分配和回收
從虛擬內(nèi)存的角度來看,頁就是最小單位。體系結(jié)構(gòu)不同,支持的頁大小也不盡相同。有些體系結(jié)構(gòu)甚至支持幾種不同的頁大小。大多數(shù)32位體系結(jié)構(gòu)支持4KB的頁,而64位體系結(jié)構(gòu)一般會(huì)支持8KB的頁。這就意味著,在支持4KB頁大小并有1GB物理內(nèi)存的機(jī)器上,物理內(nèi)存會(huì)被劃分為262144個(gè)頁。內(nèi)核用struct page結(jié)構(gòu)表示系統(tǒng)中的每個(gè)物理頁,也叫頁描述符。
??頁描述符中比較重要的域包括:
- flag域,用來存放頁面的狀態(tài),這些狀態(tài)包括頁是不是臟的,是不是被鎖定在內(nèi)存中等等;
- _count域, 存放頁的引用計(jì)數(shù)—也就是這一頁被引用了多少次。當(dāng)計(jì)數(shù)值變?yōu)?時(shí),就說明當(dāng)前內(nèi)核并沒有引用這一頁,于是,在新的分配中就可以使用它;
- virtual域, 頁的虛擬地址;
- lru域, 存放的next和prev指針,指向最近最久未使用(LRU)鏈表中的相應(yīng)結(jié)點(diǎn),這個(gè)鏈表用于頁面的回收。
必須要理解的一點(diǎn)是page結(jié)構(gòu)與物理頁相關(guān),而并非與虛擬頁相關(guān)。因此,該結(jié)構(gòu)對(duì)頁的描述只是短暫的。即使頁中所包含的數(shù)據(jù)繼續(xù)存在,但是由于交換等原因,它們可能并不再和同一個(gè)page結(jié)構(gòu)相關(guān)聯(lián)。內(nèi)核僅僅用這個(gè)數(shù)據(jù)結(jié)構(gòu)來描述當(dāng)前時(shí)刻在相關(guān)的物理頁中存放的東西。這種數(shù)據(jù)結(jié)構(gòu)的目的在于描述物理內(nèi)存本身,而不是描述包含在其中的數(shù)據(jù)。
??隨著用戶程序的執(zhí)行和結(jié)束,就需要不斷地為其分配和釋放物理頁面。內(nèi)核應(yīng)該為分配一組連續(xù)的頁面而建立一種穩(wěn)定、高效的分配策略。但是,頻繁地請(qǐng)求和釋放不同大小的一組連續(xù)頁面,必然導(dǎo)致在已分配的內(nèi)存塊中分散許多小塊的空閑頁面,即外碎片,由此帶來的問題是,即使這些小塊的空閑頁面加起來足以滿足所請(qǐng)求的頁面,但是要分配一個(gè)大塊的連續(xù)頁面可能就根本無法滿足。為此,Linux采用著名的伙伴(Buddy)算法來解決外碎片問題。
2.5.1 伙伴算法
Linux的伙伴算法把所有的空閑頁面分為10個(gè)塊鏈表,每個(gè)鏈表中的一個(gè)塊含有2的冪次個(gè)頁面,我們把這種塊叫做“頁塊”或簡稱“塊”。例如,第0個(gè)鏈表中塊的大小都為20(1個(gè)頁面),第1個(gè)鏈表中塊的大小為都為21(2個(gè)頁面),第9個(gè)鏈表中塊的大小都為2^9(512個(gè)頁面)。
??假設(shè)要求分配的塊其大小為128個(gè)頁面。該算法先在塊大小為128個(gè)頁面的鏈表中查找,看是否有這樣一個(gè)空閑塊。如果有,就直接分配;如果沒有,該算法會(huì)查找下一個(gè)更大的塊,具體地說,就是在塊大小為256個(gè)頁面的鏈表中查找一個(gè)空閑塊。如果存在這樣的空閑塊,內(nèi)核就把這256個(gè)頁面分為兩等份,一份分配出去,另一份插入到塊大小為128個(gè)頁面的鏈表中。如果在塊大小為256個(gè)頁面的鏈表中也沒有找到空閑頁塊,就繼續(xù)找更大的塊,即512個(gè)頁面的塊。如果存在這樣的塊,內(nèi)核就從512個(gè)頁面的塊中分出128個(gè)頁面滿足請(qǐng)求,然后從384個(gè)頁面中取出256個(gè)頁面插入到塊大小為256個(gè)頁面的鏈表中。然后把剩余的128個(gè)頁面插入到塊大小為128個(gè)頁面的鏈表中。如果512個(gè)頁面的鏈表中還沒有空閑塊,該算法就放棄分配,并發(fā)出出錯(cuò)信號(hào)。
??以上過程的逆過程就是塊的釋放過程,這也是該算法名字的來由。滿足以下條件的兩個(gè)塊稱為伙伴:
- 兩個(gè)塊的大小相同
- 兩個(gè)塊的物理地址連續(xù)
- 兩個(gè)快必須是從同一個(gè)更大的塊中分離出來
伙伴算法把滿足以上條件的兩個(gè)塊合并為一個(gè)塊,該算法是迭代算法,如果合并后的塊還可以跟相鄰的塊進(jìn)行合并,那么該算法就繼續(xù)合并。
2.5.2 slab機(jī)制分配
Slab有solaris操縱系統(tǒng)引入,主要是基于以下考慮:
- 內(nèi)核對(duì)內(nèi)存區(qū)的分配取決于所存放數(shù)據(jù)的類型。例如,當(dāng)給用戶態(tài)進(jìn)程分配頁面時(shí),內(nèi)核調(diào)用__get_free_pages()函數(shù),并用0填充所分配的頁面。而給內(nèi)核的數(shù)據(jù)結(jié)構(gòu)分配頁面時(shí),事情沒有這么簡單,例如,要對(duì)數(shù)據(jù)結(jié)構(gòu)所在的內(nèi)存進(jìn)行初始化、在不用時(shí)要收回它們所占用的內(nèi)存。因此,Slab中引入了對(duì)象這個(gè)概念,所謂對(duì)象就是存放一組數(shù)據(jù)結(jié)構(gòu)的內(nèi)存區(qū),其方法就是構(gòu)造或析構(gòu)函數(shù),構(gòu)造函數(shù)用于初始化數(shù)據(jù)結(jié)構(gòu)所在的內(nèi)存區(qū),而析構(gòu)函數(shù)收回相應(yīng)的內(nèi)存區(qū)。但為了便于理解,也可以把對(duì)象直接看作內(nèi)核的數(shù)據(jù)結(jié)構(gòu)。為了避免重復(fù)初始化對(duì)象,Slab分配模式并不丟棄已分配的對(duì)象,而是釋放但把它們依然保留在內(nèi)存中。當(dāng)以后又要請(qǐng)求分配同一對(duì)象時(shí),就可以從內(nèi)存獲取而不用進(jìn)行初始化。
Linux中對(duì)Slab分配模式有所改進(jìn),它對(duì)內(nèi)存區(qū)的處理并不需要進(jìn)行初始化或回收。出于效率的考慮,Linux并不調(diào)用對(duì)象的構(gòu)造或析構(gòu)函數(shù),而是把指向這兩個(gè)函數(shù)的指針都置為空。Linux中引入Slab的主要目的是為了減少對(duì)伙伴算法的調(diào)用次數(shù)。
??實(shí)際上,內(nèi)核經(jīng)常反復(fù)使用某一內(nèi)存區(qū)。例如,只要內(nèi)核創(chuàng)建一個(gè)新的進(jìn)程,就要為該進(jìn)程相關(guān)的數(shù)據(jù)結(jié)構(gòu)(PCB、打開文件對(duì)象等)分配內(nèi)存區(qū)。當(dāng)進(jìn)程結(jié)束時(shí),收回這些內(nèi)存區(qū)。因?yàn)檫M(jìn)程的創(chuàng)建和撤銷非常頻繁,因此,Linux的早期版本把大量的時(shí)間花費(fèi)在反復(fù)分配或回收這些內(nèi)存區(qū)上。從Linux2.2開始,把那些頻繁使用的頁面保存在高速緩存中并重新使用。
??可以根據(jù)對(duì)內(nèi)存區(qū)的使用頻率來對(duì)它分類。對(duì)于預(yù)期頻繁使用的內(nèi)存區(qū),可以創(chuàng)建一組特定大小的專用緩沖區(qū)進(jìn)行處理,以避免內(nèi)碎片的產(chǎn)生。對(duì)于較少使用的內(nèi)存區(qū),可以創(chuàng)建一組通用緩沖區(qū)來處理,即使這種處理模式產(chǎn)生碎片,也對(duì)整個(gè)系統(tǒng)的性能影響不大。
??硬件高速緩存的使用,又為盡量減少對(duì)伙伴算法的調(diào)用提供了另一個(gè)理由,因?yàn)閷?duì)伙伴算法的每次調(diào)用都會(huì)“弄臟”硬件高速緩存,因此,這就增加了對(duì)內(nèi)存的平均訪問次數(shù)。
??Slab分配模式把對(duì)象分組放進(jìn)緩沖區(qū),因?yàn)榫彌_區(qū)的組織和管理與硬件高速緩存的命中率密切相關(guān),因此,Slab緩沖區(qū)并非由各個(gè)對(duì)象直接構(gòu)成,而是由一連串的“大塊(Slab)”構(gòu)成,而每個(gè)大塊中則包含了若干個(gè)同種類型的對(duì)象,這些對(duì)象或已被分配,或空閑,如圖4.12所示。一般而言,對(duì)象分兩種,一種是大對(duì)象,一種是小對(duì)象。所謂小對(duì)象,是指在一個(gè)頁面中可以容納下好幾個(gè)對(duì)象的那種。例如,一個(gè)inode結(jié)構(gòu)大約占300多個(gè)字節(jié),因此,一個(gè)頁面中可以容納8個(gè)以上的inode結(jié)構(gòu),因此,inode結(jié)構(gòu)就為小對(duì)象。Linux內(nèi)核中把小于512字節(jié)的對(duì)象叫做小對(duì)象,大于512字節(jié)的對(duì)象叫做大對(duì)象。

實(shí)際上,緩沖區(qū)就是主存中的一片區(qū)域,把這片區(qū)域劃分為多個(gè)塊,每塊就是一個(gè)Slab,每個(gè)Slab由一個(gè)或多個(gè)頁面組成,每個(gè)Slab中存放的就是對(duì)象。Linux把緩沖區(qū)分為專用和通用,它們分別用于不同的目的:
- 專用緩沖區(qū)主要用于頻繁使用的數(shù)據(jù)結(jié)構(gòu),如task_struct、mm_struct、vm_area_struct、 file、 dentry、 inode等
- 在內(nèi)核中初始化開銷不大的數(shù)據(jù)結(jié)構(gòu)可以合用一個(gè)通用的緩沖區(qū)。通用緩沖區(qū)最小的為32字節(jié),然后依次為64、128、…直至128K(即32個(gè)頁面),但是,對(duì)通用緩沖區(qū)的管理又采用的是Slab方式。
2.6 交換機(jī)制
當(dāng)物理內(nèi)存出現(xiàn)不足時(shí),Linux 內(nèi)存管理子系統(tǒng)需要釋放部分物理內(nèi)存頁面。這一任務(wù)由內(nèi)核的交換守護(hù)進(jìn)程 kswapd 完成,該內(nèi)核守護(hù)進(jìn)程實(shí)際是一個(gè)內(nèi)核線程,它在內(nèi)核初始化時(shí)啟動(dòng),并周期地運(yùn)行。它的任務(wù)就是保證系統(tǒng)中具有足夠的空閑頁面,從而使內(nèi)存管理子系統(tǒng)能夠有效運(yùn)行。
在Linux中,交換的單位是頁面而不是進(jìn)程。盡管交換的單位是頁面,但交換還是要付出一定的代價(jià),尤其是時(shí)間的代價(jià)。實(shí)際上,在操作系統(tǒng)中,時(shí)間和空間是一對(duì)矛盾,常常需要在二者之間作出平衡,有時(shí)需要以空間換時(shí)間,有時(shí)需要以時(shí)間換空間,頁面交換就是典型的以時(shí)間換空間。這里要說明的是,頁面交換是不得已而為之,例如在時(shí)間要求比較緊急的實(shí)時(shí)系統(tǒng)中,是不宜采用頁面交換機(jī)制的,因?yàn)樗钩绦虻膱?zhí)行在時(shí)間上有了較大的不確定性。因此,Linux給用戶提供了一種選擇,可以通過命令或系統(tǒng)調(diào)用開啟或關(guān)閉交換機(jī)制。
??在頁面交換中,頁面置換算法是影響交換性能的關(guān)鍵性指標(biāo),其復(fù)雜性主要與換出有關(guān)。具體說來,必須考慮三個(gè)主要問題:
- 哪種頁面要換出
- 如何在交換區(qū)中存放頁面
- 如何選擇被交換出的頁面
請(qǐng)注意,我們?cè)谶@里所提到的頁或頁面指的是其中存放的數(shù)據(jù),因此,所謂頁面的換入換出實(shí)際上是指頁面中數(shù)據(jù)的換入換出。
2.6.1 哪種頁面被換出
交換的最終目的是頁面的回收,只有與用戶空間建立了映射關(guān)系的物理頁面才會(huì)被換出去,而內(nèi)核空間中內(nèi)核所占的頁面則常駐內(nèi)存??梢园延脩艨臻g中的頁面按其內(nèi)容和性質(zhì)分為以下幾種:
- 進(jìn)程映像所占的頁面,包括進(jìn)程的代碼段、數(shù)據(jù)段、堆棧段以及動(dòng)態(tài)分配的“存儲(chǔ)堆”
- 通過系統(tǒng)調(diào)用mmap()把文件的內(nèi)容映射到用戶空間
- 進(jìn)程間共享內(nèi)存區(qū)
對(duì)于第1種情況,進(jìn)程的代碼段數(shù)據(jù)段所占的內(nèi)存頁面可以被換入換出,但堆棧所占的頁面一般不被換出,因?yàn)檫@樣可以簡化內(nèi)核的設(shè)計(jì)。
對(duì)于第2種情況,這些頁面所使用的交換區(qū)就是被映射的文件本身。
對(duì)于第3種情況,其頁面的換入換出比較復(fù)雜。
與此相對(duì)照,映射到內(nèi)核空間中的頁面都不會(huì)被換出。
2.6.2 如何在交換區(qū)中存放頁面
我們知道物理內(nèi)存被劃分為若干頁面,每個(gè)頁面的大小為4KB。實(shí)際上,交換區(qū)也被劃分為塊,每個(gè)塊的大小正好等于一頁,我們把交換區(qū)中的一塊叫做一個(gè)頁插槽(page slot),意思是說,把一個(gè)物理頁面插入到一個(gè)插槽中。當(dāng)進(jìn)行換出時(shí),內(nèi)核盡可能把換出的頁放在相鄰的插槽中,從而減少在訪問交換區(qū)時(shí)磁盤的尋道時(shí)間。
2.6.3 如何選擇被交換出的頁面
頁面交換是非常復(fù)雜的,其主要內(nèi)容之一就是如何選擇要換出的頁面,我們以循序漸進(jìn)的方式來討論頁面交換策略的選擇。
??策略一,需要時(shí)才交換。每當(dāng)缺頁異常發(fā)生時(shí),就給它分配一個(gè)物理頁面。如果發(fā)現(xiàn)沒有空閑的頁面可供分配,就設(shè)法將一個(gè)或多個(gè)內(nèi)存頁面換出到磁盤上,從而騰出一些內(nèi)存頁面來。這種交換策略確實(shí)簡單,但有一個(gè)明顯的缺點(diǎn),這是一種被動(dòng)的交換策略,需要時(shí)才交換,系統(tǒng)勢必要付出相當(dāng)多的時(shí)間進(jìn)行換入換出。
??策略二,系統(tǒng)空閑時(shí)交換。與策略一相比較,這是一種積極的交換策略,也就是,在系統(tǒng)空閑時(shí),預(yù)先換出一些頁面而騰出一些內(nèi)存頁面,從而在內(nèi)存中維持一定的空閑頁面供應(yīng)量,使得在缺頁中斷發(fā)生時(shí)總有空閑頁面可供使用。至于換出頁面的選擇,一般都采用LRU算法。但是這種策略實(shí)施起來也有困難,因?yàn)椴]有哪種方法能準(zhǔn)確地預(yù)測對(duì)頁面的訪問,所以,完全可能發(fā)生這樣的情況,即一個(gè)好久沒有受到訪問的頁面剛剛被換出去,卻又要訪問它了,于是又把它換進(jìn)來。在最壞的情況下,有可能整個(gè)系統(tǒng)的處理能力都被這樣的換入/換出所影響,而根本不能進(jìn)行有效的計(jì)算和操作。這種現(xiàn)象被稱為頁面的“抖動(dòng)”。
??策略三,換出但并不立即釋放。當(dāng)系統(tǒng)挑選出若干頁面進(jìn)行換出時(shí),將相應(yīng)的頁面寫入磁盤交換區(qū)中,并修改相應(yīng)頁表中頁表項(xiàng)的內(nèi)容(把present標(biāo)志位置為0),但是并不立即釋放,而是將其page結(jié)構(gòu)留在一個(gè)緩沖(cache)隊(duì)列中,使其從活躍(active)狀態(tài)轉(zhuǎn)為不活躍(Inactive)狀態(tài)。至于這些頁面的最后釋放,要推遲到必要時(shí)才進(jìn)行。這樣,如果一個(gè)頁面在釋放后又立即受到訪問,就可以從物理頁面的緩沖隊(duì)列中找到相應(yīng)的頁面,再次為之建立映射。由于此頁面尚未釋放,還保留著原來的內(nèi)容,就不需要磁盤讀入了。經(jīng)過一段時(shí)間以后,一個(gè)不活躍的內(nèi)存頁面一直沒有受到訪問,那這個(gè)頁面就需要真正被釋放了。
??策略四,把頁面換出推遲到不能再推遲為止。實(shí)際上,策略三還有值得改進(jìn)的地方。首先在換出頁面時(shí)不一定要把它的內(nèi)容寫入磁盤。如果一個(gè)頁面自從最近一次換入后并沒有被寫過(如代碼),那么這個(gè)頁面是“干凈的”,就沒有必要把它寫入磁盤。其次,即使“臟”頁面,也沒有必要立即寫出去,可以采用策略三。至于“干凈”頁面,可以一直緩沖到必要時(shí)才加以回收,因?yàn)榛厥找粋€(gè)“干凈”頁面花費(fèi)的代價(jià)很小。
2.6.4 頁面交換守護(hù)進(jìn)程kswapd
為了避免在CPU忙碌的時(shí)候,也就是在缺頁異常發(fā)生時(shí),臨時(shí)搜索可供換出的內(nèi)存頁面并加以換出,Linux內(nèi)核定期地檢查系統(tǒng)內(nèi)的空閑頁面數(shù)是否小于預(yù)定義的極限,一旦發(fā)現(xiàn)空閑頁面數(shù)太少,就預(yù)先將若干頁面換出,以減輕缺頁異常發(fā)生時(shí)系統(tǒng)所承受的負(fù)擔(dān)。
??Kswapd的執(zhí)行路線分為兩部分,第一部分是發(fā)現(xiàn)物理頁面已經(jīng)短缺的情況下才進(jìn)行的,目的在于預(yù)先找出若干頁面,且將這些頁面的映射斷開,使這些物理頁面從活躍狀態(tài)轉(zhuǎn)入不活躍狀態(tài),為頁面的換出做好準(zhǔn)備。第二部分是每次都要執(zhí)行的,目的在于把已經(jīng)處于不活躍狀態(tài)的“臟” 頁面寫入交換區(qū),使他們成為不活躍的“干凈”頁面繼續(xù)緩沖,或進(jìn)一步回收這樣的頁面成為空閑頁面。
3 其他
3.1 Linux內(nèi)存相關(guān)命令
3.1.1 free
free 執(zhí)行命令顯示結(jié)果如下:
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 | 總物理內(nèi)存 | 當(dāng)前使用的內(nèi)存(包括slab+buffers+cached) | 完全沒有使用的內(nèi)存 | 進(jìn)程間共享的內(nèi)存 | 緩存文件的元數(shù)據(jù) | 緩存文件的具體內(nèi)容 |
| -/+ buffers/cache | 當(dāng)前使用的內(nèi)存(不包括buffers+cached,但包括slab) | 未使用和緩存的內(nèi)存(free+buffers+cached) | ||||
| swap | 總的交換空間 | 已使用的交換空間 | 未使用的交換空間 |
系統(tǒng)實(shí)際可以使用的內(nèi)存之和是:
avaiable_phisical_memory = free + buffers + cached + slab
- buffer :作為buffer cache的內(nèi)存,是塊設(shè)備的讀寫緩沖區(qū),在沒有文件系統(tǒng)的情況下,直接對(duì)磁盤進(jìn)行操作的數(shù)據(jù)會(huì)緩存到buffer cache中,例如,文件系統(tǒng)的元數(shù)據(jù)都會(huì)緩存到buffer cache中。
- cache:作為page cache的內(nèi)存,是文件的緩存,在文件層面上的數(shù)據(jù)會(huì)緩存到page cache。文件的邏輯層需要映射到實(shí)際的物理磁盤,這種映射關(guān)系由文件系統(tǒng)來完成。
- slab: 內(nèi)核緩存,用戶快速創(chuàng)建內(nèi)核中的對(duì)象所用。
如果 cache 的值很大,說明cache住的文件數(shù)很多。如果頻繁訪問到的文件都能被cache住,那么磁盤的讀IO 必會(huì)非常小。
可以使用下面命令主動(dòng)釋放 cache占用的內(nèi)存
echo 1 > /proc/sys/vm/drop_caches
可以使用下面命令主動(dòng)釋放flush buffer中的內(nèi)容。
sync
可以使用下面命令主動(dòng)釋放 slab占用的內(nèi)存
echo 2 > /proc/sys/vm/drop_caches
3.1.2 /proc/meminfo
/proc/meminfo是了解Linux系統(tǒng)內(nèi)存使用狀況的主要接口,我們最常用的”free”、”vmstat”等命令就是通過它獲取數(shù)據(jù)的 ,/proc/meminfo所包含的信息比”free”等命令要豐富得多。
cat /proc/meminfo ,輸出內(nèi)容如下:
MemTotal: 510080 kB(總的內(nèi)存)
MemFree: 17924 kB(未使用的內(nèi)存)
Buffers: 4644 kB(用來給文件做緩沖內(nèi)存)
Cached: 35104 kB(被高速緩沖存儲(chǔ)器(cache memory)用的內(nèi)存的大小)
SwapCached: 4540 kB(交換空間)
Active: 330988 kB(活躍使用中的緩沖或高速緩沖存儲(chǔ)器頁面文件的大?。?Inactive: 137500 kB(不經(jīng)常使用中的緩沖或高速緩沖存儲(chǔ)器頁面文件的大小,可能被用于其他途徑)
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是一個(gè)查看虛擬內(nèi)存(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:顯示活躍和非活躍內(nèi)存
-f:顯示從系統(tǒng)啟動(dòng)至今的fork數(shù)量 。
-m:顯示slabinfo
-n:只在開始時(shí)顯示一次各字段名稱。
-s:顯示內(nèi)存相關(guān)統(tǒng)計(jì)信息及多種系統(tǒng)活動(dòng)數(shù)量。
delay:刷新時(shí)間間隔。如果不指定,只顯示一條結(jié)果。
count:刷新次數(shù)。如果不指定刷新次數(shù),但指定了刷新時(shí)間間隔,這時(shí)刷新次數(shù)為無窮。
-d:顯示磁盤相關(guān)統(tǒng)計(jì)信息。
-p:顯示指定磁盤分區(qū)統(tǒng)計(jì)信息
-S:使用指定單位顯示。參數(shù)有 k 、K 、m 、M ,分別代表1000、1024、1000000、1048576字節(jié)(byte)。默認(rèn)單位為K(1024 bytes)
-V:顯示vmstat版本信息。
下圖顯示一個(gè)用法實(shí)例:

字段說明:
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下常用的性能分析工具,能夠?qū)崟r(shí)顯示系統(tǒng)中各個(gè)進(jìn)程的資源占用狀況,包括整體和單個(gè)進(jìn)程。
top命令執(zhí)行結(jié)果示例如下:

第一行是任務(wù)隊(duì)列信息,同 uptime 命令的執(zhí)行結(jié)果。其內(nèi)容如下:
| 字段 | 說明 |
|---|---|
| 09:44:34 | 當(dāng)前時(shí)間 |
| up 58 days,22:26 | 系統(tǒng)運(yùn)行時(shí)間 |
| 1 user | 當(dāng)前登錄用戶數(shù) |
| load average: 2.02, 2.07, 2.04 | 系統(tǒng)負(fù)載,即任務(wù)隊(duì)列的平均長度。三個(gè)數(shù)值分別為 1分鐘、5分鐘、15分鐘前到現(xiàn)在的平均值。 |
第二、三行為進(jìn)程和CPU的信息
| 字段 | 說明 |
|---|---|
| Tasks: 202 total | 進(jìn)程總數(shù)量 |
| 1 running | 正在運(yùn)行的進(jìn)程數(shù) |
| 201 sleeping | 休眠的進(jìn)程數(shù) |
| 0 stopped | 停止的進(jìn)程數(shù) |
| 0 zombie | 僵尸進(jìn)程數(shù) |
| Cps(s): 3.4% us | 用戶空間占用CPU百分比 |
| 2.5% sy | 內(nèi)核空間占用cpu百分比 |
| 0.6% ni | 用戶進(jìn)程空間內(nèi)改變過優(yōu)先級(jí)的進(jìn)程占用CPU百分比第二、三行為進(jìn)程和CPU的信息 |
| 93.5 % id | 空閑CPU百分比 |
| 0.0% wa | 等待輸入輸出的CPU時(shí)間百分比 |
| 0.0% hi | 硬件CPU中斷占用百分比 |
| 0.1% si | 軟中斷占用百分比 |
最后兩行為內(nèi)存信息,和free類似,不在做說明。
統(tǒng)計(jì)信息區(qū)域的下方顯示了各個(gè)進(jìn)程的詳細(xì)信息
序號(hào) 列名 含義
a PID 進(jìn)程id
b PPID 父進(jìn)程id
c RUSER Real user name
d UID 進(jìn)程所有者的用戶id
e USER 進(jìn)程所有者的用戶名
f GROUP 進(jìn)程所有者的組名
g TTY 啟動(dòng)進(jìn)程的終端名。不是從終端啟動(dòng)的進(jìn)程則顯示為 ?
h PR 優(yōu)先級(jí)
i NI nice值。負(fù)值表示高優(yōu)先級(jí),正值表示低優(yōu)先級(jí)
j P 最后使用的CPU,僅在多CPU環(huán)境下有意義
k %CPU 上次更新到現(xiàn)在的CPU時(shí)間占用百分比
l TIME 進(jìn)程使用的CPU時(shí)間總計(jì),單位秒
m TIME+ 進(jìn)程使用的CPU時(shí)間總計(jì),單位1/100秒
n %MEM 進(jìn)程使用的物理內(nèi)存百分比
o VIRT 進(jìn)程使用的虛擬內(nèi)存總量,單位kb。VIRT=SWAP+RES
p SWAP 進(jìn)程使用的虛擬內(nèi)存中,被換出的大小,單位kb。
q RES 進(jìn)程使用的、未被換出的物理內(nèi)存大小,單位kb。RES=CODE+DATA
r CODE 可執(zhí)行代碼占用的物理內(nèi)存大小,單位kb
s DATA 可執(zhí)行代碼以外的部分(數(shù)據(jù)段+棧)占用的物理內(nèi)存大小,單位kb
t SHR 共享內(nèi)存大小,單位kb
u nFLT 頁面錯(cuò)誤次數(shù)
v nDRT 最后一次寫入到現(xiàn)在,被修改過的頁面數(shù)。
w S 進(jìn)程狀態(tài)(D=不可中斷的睡眠狀態(tài),R=運(yùn)行,S=睡眠,T=跟蹤/停止,Z=僵尸進(jìn)程)
x COMMAND 命令名/命令行
y WCHAN 若該進(jìn)程在睡眠,則顯示睡眠中的系統(tǒng)函數(shù)名
z Flags 任務(wù)標(biāo)志,參考 sched.h
附常用操作:
top //每隔5秒顯式所有進(jìn)程的資源占用情況
top -d 2 //每隔2秒顯式所有進(jìn)程的資源占用情況
top -c //每隔5秒顯式進(jìn)程的資源占用情況,并顯示進(jìn)程的命令行參數(shù)(默認(rèn)只有進(jìn)程名)
top -p 12345 -p 6789//每隔5秒顯示pid是12345和pid是6789的兩個(gè)進(jìn)程的資源占用情況
top -d 2 -c -p 123456 //每隔2秒顯示pid是12345的進(jìn)程的資源使用情況,并顯式該進(jìn)程啟動(dòng)的命令行參數(shù)
3.1.5 pmap
pmap 用于報(bào)道進(jìn)程的內(nèi)存映射關(guān)系。
用法
pmap PID
或者
pmap [options] PID
在輸出中它顯示全部的地址,kbytes,mode還有mapping。
選項(xiàng)
-x extended顯示擴(kuò)展格式
-d device顯示設(shè)備格式
-q quiet不顯示header/footer行
-V 顯示版本信息
擴(kuò)展和設(shè)備格式區(qū)域
Address: 內(nèi)存開始地址
Kbytes: 占用內(nèi)存的字節(jié)數(shù)(KB)
RSS: 保留內(nèi)存的字節(jié)數(shù)(KB)
Dirty: 臟頁的字節(jié)數(shù)(包括共享和私有的)(KB)
Mode: 內(nèi)存的權(quán)限:read、write、execute、shared、private (寫時(shí)復(fù)制)
Mapping: 占用內(nèi)存的文件、或[anon](分配的內(nèi)存)、或[stack](堆棧)
Offset: 文件偏移
Device: 設(shè)備名 (major:minor)
3.1.6 ps
名稱:ps
使用權(quán)限:所有使用者
使用方式:ps [options] [--help]
說明:顯示瞬間行程 (process) 的動(dòng)態(tài)
參數(shù):ps的參數(shù)非常多, 在此僅列出幾個(gè)常用的參數(shù)并大略介紹含義
-A 列出所有的進(jìn)程
-w 顯示加寬可以顯示較多的資訊
-au 顯示較詳細(xì)的資訊
-aux 顯示所有包含其他使用者的行程
常用參數(shù):
-A 顯示所有進(jìn)程(等價(jià)于-e)(utility)
-a 顯示一個(gè)終端的所有進(jìn)程,除了會(huì)話引線
-N 忽略選擇。
-d 顯示所有進(jìn)程,但省略所有的會(huì)話引線(utility)
-x 顯示沒有控制終端的進(jìn)程,同時(shí)顯示各個(gè)命令的具體路徑。dx不可合用。(utility)
-p pid 進(jìn)程使用cpu的時(shí)間
-u uid or username 選擇有效的用戶id或者是用戶名
-g gid or groupname 顯示組的所有進(jìn)程。
U username 顯示該用戶下的所有進(jìn)程,且顯示各個(gè)命令的詳細(xì)路徑。如:ps U zhang;(utility)
-f 全部列出,通常和其他選項(xiàng)聯(lián)用。如:ps -fa or ps -fx and so on.
-l 長格式(有F,wchan,C 等字段)
-j 作業(yè)格式
-o 用戶自定義格式。
v 以虛擬存儲(chǔ)器格式顯示
s 以信號(hào)格式顯示
-m 顯示所有的線程
-H 顯示進(jìn)程的層次(和其它的命令合用,如:ps -Ha)(utility)
e 命令之后顯示環(huán)境(如:ps -d e; ps -a e)(utility)
h 不顯示第一行
ps aux 輸出格式如下:

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

3.2 overcommit
Memory Overcommit的意思是操作系統(tǒng)承諾給進(jìn)程的內(nèi)存大小超過了實(shí)際可用的內(nèi)存。一個(gè)保守的操作系統(tǒng)不會(huì)允許memory overcommit,有多少就分配多少,再申請(qǐng)就沒有了,這其實(shí)有些浪費(fèi)內(nèi)存,因?yàn)檫M(jìn)程實(shí)際使用到的內(nèi)存往往比申請(qǐng)的內(nèi)存要少,比如某個(gè)進(jìn)程malloc()了200MB內(nèi)存,但實(shí)際上只用到了100MB,按照UNIX/Linux的算法,物理內(nèi)存頁的分配發(fā)生在使用的瞬間,而不是在申請(qǐng)的瞬間,也就是說未用到的100MB內(nèi)存根本就沒有分配,這100MB內(nèi)存就閑置了。下面這個(gè)概念很重要,是理解memory overcommit的關(guān)鍵:commit(或overcommit)針對(duì)的是內(nèi)存申請(qǐng),內(nèi)存申請(qǐng)不等于內(nèi)存分配,內(nèi)存只在實(shí)際用到的時(shí)候才分配。
??Linux是允許memory overcommit的,只要你來申請(qǐng)內(nèi)存我就給你,寄希望于進(jìn)程實(shí)際上用不到那么多內(nèi)存,但萬一用到那么多了呢?那就會(huì)發(fā)生類似“銀行擠兌”的危機(jī),現(xiàn)金(內(nèi)存)不足了。Linux設(shè)計(jì)了一個(gè)OOM killer機(jī)制(OOM = out-of-memory)來處理這種危機(jī):挑選一個(gè)進(jìn)程出來殺死,以騰出部分內(nèi)存,如果還不夠就繼續(xù)殺…也可通過設(shè)置內(nèi)核參數(shù) vm.panic_on_oom 使得發(fā)生OOM時(shí)自動(dòng)重啟系統(tǒng)。
內(nèi)核參數(shù) vm.overcommit_memory 接受三種取值
- 0 – Heuristic overcommit handling. 這是缺省值,它允許overcommit,但過于明目張膽的overcommit會(huì)被拒絕,比如malloc一次性申請(qǐng)的內(nèi)存大小就超過了系統(tǒng)總內(nèi)存。Heuristic的意思是“試探式的”,內(nèi)核利用某種算法(對(duì)該算法的詳細(xì)解釋請(qǐng)看文末)猜測你的內(nèi)存申請(qǐng)是否合理,它認(rèn)為不合理就會(huì)拒絕overcommit。
- 1 – Always overcommit. 允許overcommit,對(duì)內(nèi)存申請(qǐng)來者不拒。
- 2 – Don’t overcommit. 禁止overcommit。
系統(tǒng)具體使用配置的值可以通過這個(gè)文件 /proc/sys/vm/overcommit_memory查看
內(nèi)核參數(shù)vm.overcommit_ratio 只有當(dāng)vm.overcommit_memory = 2的時(shí)候才會(huì)生效,內(nèi)存可申請(qǐng)內(nèi)存為
SWAP內(nèi)存大小 + 物理內(nèi)存 * overcommit_ratio/100
查看系統(tǒng)overcommit信息
grep -i commit /proc/meminfo
CommitLimit: 517584 kB
Committed_AS: 3306488 kB
CommitLimit:最大能分配的內(nèi)存(僅在vm.overcommit_memory=2時(shí)候生效),具體的值是
SWAP內(nèi)存大小 + 物理內(nèi)存 * overcommit_ratio / 100
Committed_AS:當(dāng)前已經(jīng)分配的內(nèi)存大小
4 參考
- https://www.gitbook.com/book/xylinuxer/lkpa
- http://cizixs.com/2015/10/01/linux-memory-management-through-free
- https://www.mawenbao.com/research/linux-ate-my-memory.html#fn1
- http://www.cnblogs.com/chenpingzhao/p/5161844.html
- http://www.binarytides.com/linux-command-check-memory-usage/
- http://www.ha97.com/4512.html
- http://www.cnblogs.com/ggjucheng/archive/2012/01/05/2312625.html
- http://www.linuxidc.com/Linux/2011-03/33582.htm
- http://linuxperf.com/?p=102
