[轉帖]運維必讀:Linux 的內存分頁管理

運維必讀:Linux 的內存分頁管理

內存是計算機的主存儲器。內存爲進程開闢出進程空間,讓進程在其中保存數據。我將從內存的物理特性出發,深刻到內存管理的細節,特別是瞭解虛擬內存和內存分頁的概念。安全

▉內存markdown

簡單地說,內存就是一個數據貨架。內存有一個最小的存儲單位,大多數都是一個字節。內存用內存地址(memory address)來爲每一個字節的數據順序編號。所以,內存地址說明了數據在內存中的位置。內存地址從0開始,每次增長1。這種線性增長的存儲器地址稱爲線性地址(linear address)。爲了方便,咱們用十六進制數來表示內存地址,好比0x0000000三、0x1A010CB0。這裏的「0x」用來表示十六進制。「0x」後面跟着的,就是做爲內存地址的十六進制數。網絡

內存地址的編號有上限。地址空間的範圍和地址總線(address bus)的位數直接相關。CPU經過地址總線來向內存說明想要存取數據的地址。以英特爾32位的80386型CPU爲例,這款CPU有32個針腳能夠傳輸地址信息。每一個針腳對應了一位。若是針腳上是高電壓,那麼這一位是1。若是是低電壓,那麼這一位是0。32位的電壓高低信息經過地址總線傳到內存的32個針腳,內存就能把電壓高低信息轉換成32位的二進制數,從而知道CPU想要的是哪一個位置的數據。用十六進制表示,32位地址空間就是從0x00000000 到0xFFFFFFFF。數據結構

內存的存儲單元採用了隨機讀取存儲器(RAM, Random Access Memory)。所謂的「隨機讀取」,是指存儲器的讀取時間和數據所在位置無關。與之相對,不少存儲器的讀取時間和數據所在位置有關。就拿磁帶來講,咱們想聽其中的一首歌,必須轉動帶子。若是那首歌是第一首,那麼當即就能夠播放。若是那首歌恰巧是最後一首,咱們快進到能夠播放的位置就須要花很長時間。咱們已經知道,進程須要調用內存中不一樣位置的數據。若是數據讀取時間和位置相關的話,計算機就很難把控進程的運行時間。所以,隨機讀取的特性是內存成爲主存儲器的關鍵因素。運維

內存提供的存儲空間,除了能知足內核的運行需求,還一般能支持運行中的進程。即便進程所需空間超過內存空間,內存空間也能夠經過少許拓展來彌補。換句話說,內存的存儲能力,和計算機運行狀態的數據總量至關。內存的缺點是不能持久地保存數據。一旦斷電,內存中的數據就會消失。所以,計算機即便有了內存這樣一個主存儲器,仍是須要硬盤這樣的外部存儲器來提供持久的儲存空間。dom

▉虛擬內存spa

內存的一項主要任務,就是存儲進程的相關數據。咱們以前已經看到過進程空間的程序段、全局數據、棧和堆,以及這些這些存儲結構在進程運行中所起到的關鍵做用。有趣的是,儘管進程和內存的關係如此緊密,但進程並不能直接訪問內存。在Linux下,進程不能直接讀寫內存中地址爲0x1位置的數據。進程中能訪問的地址,只能是虛擬內存地址(virtual memory address)。操做系統會把虛擬內存地址翻譯成真實的內存地址。這種內存管理方式,稱爲虛擬內存(virtual memory)。操作系統

每一個進程都有本身的一套虛擬內存地址,用來給本身的進程空間編號。進程空間的數據一樣以字節爲單位,依次增長。從功能上說,虛擬內存地址和物理內存地址相似,都是爲數據提供位置索引。進程的虛擬內存地址相互獨立。所以,兩個進程空間能夠有相同的虛擬內存地址,如0x10001000。虛擬內存地址和物理內存地址又有必定的對應關係,如圖1所示。對進程某個虛擬內存地址的操做,會被CPU翻譯成對某個具體內存地址的操做。翻譯

圖1 虛擬內存地址和物理內存地址的對應設計

應用程序來講對物理內存地址一無所知。它只可能經過虛擬內存地址來進行數據讀寫。程序中表達的內存地址,也都是虛擬內存地址。進程對虛擬內存地址的操做,會被操做系統翻譯成對某個物理內存地址的操做。因爲翻譯的過程由操做系統全權負責,因此應用程序能夠在全過程當中對物理內存地址一無所知。所以,C程序中表達的內存地址,都是虛擬內存地址。好比在C語言中,能夠用下面指令來打印變量地址:

int v = 0; printf("%p", (void*)&v);

本質上說,虛擬內存地址剝奪了應用程序自由訪問物理內存地址的權利。進程對物理內存的訪問,必須通過操做系統的審查。所以,掌握着內存對應關係的操做系統,也掌握了應用程序訪問內存的閘門。藉助虛擬內存地址,操做系統能夠保障進程空間的獨立性。只要操做系統把兩個進程的進程空間對應到不一樣的內存區域,就讓兩個進程空間成爲「老死不相往來」的兩個小王國。兩個進程就不可能相互篡改對方的數據,進程出錯的可能性就大爲減小。

另外一方面,有了虛擬內存地址,內存共享也變得簡單。操做系統能夠把同一物理內存區域對應到多個進程空間。這樣,不須要任何的數據複製,多個進程就能夠看到相同的數據。內核和共享庫的映射,就是經過這種方式進行的。每一個進程空間中,最初一部分的虛擬內存地址,都對應到物理內存中預留給內核的空間。這樣,全部的進程就能夠共享同一套內核數據。共享庫的狀況也是相似。對於任何一個共享庫,計算機只須要往物理內存中加載一次,就能夠經過操縱對應關係,來讓多個進程共同使用。IPO中的共享內存,也有賴於虛擬內存地址。

▉內存分頁

虛擬內存地址和物理內存地址的分離,給進程帶來便利性和安全性。但虛擬內存地址和物理內存地址的翻譯,又會額外耗費計算機資源。在多任務的現代計算機中,虛擬內存地址已經成爲必備的設計。那麼,操做系統必需要考慮清楚,如何能高效地翻譯虛擬內存地址。

記錄對應關係最簡單的辦法,就是把對應關係記錄在一張表中。爲了讓翻譯速度足夠地快,這個表必須加載在內存中。不過,這種記錄方式驚人地浪費。若是樹莓派1GB物理內存的每一個字節都有一個對應記錄的話,那麼光是對應關係就要遠遠超過內存的空間。因爲對應關係的條目衆多,搜索到一個對應關係所需的時間也很長。這樣的話,會讓樹莓派陷入癱瘓。

所以,Linux採用了分頁(paging)的方式來記錄對應關係。所謂的分頁,就是以更大尺寸的單位頁(page)來管理內存。在Linux中,一般每頁大小爲4KB。若是想要獲取當前樹莓派的內存頁大小,可使用命令:

$getconf PAGE_SIZE

獲得結果,即內存分頁的字節數:

4096

返回的4096表明每一個內存頁能夠存放4096個字節,即4KB。Linux把物理內存和進程空間都分割成頁。

內存分頁,能夠極大地減小所要記錄的內存對應關係。咱們已經看到,以字節爲單位的對應記錄實在太多。若是把物理內存和進程空間的地址都分紅頁,內核只須要記錄頁的對應關係,相關的工做量就會大爲減小。因爲每頁的大小是每一個字節的4000倍。所以,內存中的總頁數只是總字節數的四千分之一。對應關係也縮減爲原始策略的四千分之一。分頁讓虛擬內存地址的設計有了實現的可能。

不管是虛擬頁,仍是物理頁,一頁以內的地址都是連續的。這樣的話,一個虛擬頁和一個物理頁對應起來,頁內的數據就能夠按順序一一對應。這意味着,虛擬內存地址和物理內存地址的末尾部分應該徹底相同。大多數狀況下,每一頁有4096個字節。因爲4096是2的12次方,因此地址最後12位的對應關係自然成立。咱們把地址的這一部分稱爲偏移量(offset)。偏移量實際上表達了該字節在頁內的位置。地址的前一部分則是頁編號。操做系統只須要記錄頁編號的對應關係。

圖2 地址翻譯過程

▉多級分頁表

內存分頁制度的關鍵,在於管理進程空間頁和物理頁的對應關係。操做系統把對應關係記錄在分頁表(page table)中。這種對應關係讓上層的抽象內存和下層的物理內存分離,從而讓Linux能靈活地進行內存管理。因爲每一個進程會有一套虛擬內存地址,那麼每一個進程都會有一個分頁表。爲了保證查詢速度,分頁表也會保存在內存中。分頁表有不少種實現方式,最簡單的一種分頁表就是把全部的對應關係記錄到同一個線性列表中,即如圖2中的「對應關係」部分所示。

這種單一的連續分頁表,須要給每個虛擬頁預留一條記錄的位置。但對於任何一個應用進程,其進程空間真正用到的地址都至關有限。咱們還記得,進程空間會有棧和堆。進程空間爲棧和堆的增加預留了地址,但棧和堆不多會佔滿進程空間。這意味着,若是使用連續分頁表,不少條目都沒有真正用到。所以,Linux中的分頁表,採用了多層的數據結構。多層的分頁表可以減小所需的空間。

咱們來看一個簡化的分頁設計,用以說明Linux的多層分頁表。咱們把地址分爲了頁編號和偏移量兩部分,用單層的分頁表記錄頁編號部分的對應關係。對於多層分頁表來講,會進一步分割頁編號爲兩個或更多的部分,而後用兩層或更多層的分頁表來記錄其對應關係,如圖3所示。

圖3 多層分頁表

在圖3的例子中,頁編號分紅了兩級。第一級對應了前8位頁編號,用2個十六進制數字表示。第二級對應了後12位頁編號,用3個十六進制編號。二級表記錄有對應的物理頁,即保存了真正的分頁記錄。二級表有不少張,每一個二級表分頁記錄對應的虛擬地址前8位都相同。好比二級表0x00,裏面記錄的前8位都是0x00。翻譯地址的過程要跨越兩級。咱們先取地址的前8位,在一級表中找到對應記錄。該記錄會告訴咱們,目標二級表在內存中的位置。咱們再在二級表中,經過虛擬地址的後12位,找到分頁記錄,從而最終找到物理地址。

多層分頁表就好像把完整的電話號碼分紅區號。咱們把同一地區的電話號碼以及對應的人名記錄同通一個小本子上。再用一個上級本子記錄區號和各個小本子的對應關係。若是某個區號沒有使用,那麼咱們只須要在上級本子上把該區號標記爲空。一樣,一級分頁表中0x01記錄爲空,說明了以0x01開頭的虛擬地址段沒有使用,相應的二級表就不須要存在。正是經過這一手段,多層分頁表佔據的空間要比單層分頁表少了不少。

多層分頁表還有另外一個優點。單層分頁表必須存在於連續的內存空間。而多層分頁表的二級表,能夠散步於內存的不一樣位置。這樣的話,操做系統就能夠利用零碎空間來存儲分頁表。還須要注意的是,這裏簡化了多層分頁表的不少細節。最新Linux系統中的分頁表多達3層,管理的內存地址也比本章介紹的長不少。不過,多層分頁表的基本原理都是相同。

 

*聲明:推送內容及圖片來源於網絡,部份內容會有所改動,版權歸原做者全部,如來源信息有誤或侵犯權益,請聯繫咱們刪除或受權事宜。

- END -

相關文章
相關標籤/搜索