天天看點

linux 記憶體管理之基礎篇

一.Linux記憶體管理的一些基本概念

記憶體空間:

            絕大多數的嵌入式系統的系統記憶體和I/O位址空間是統一編址的,記憶體和I/O位址空間共享0x00000000~0xFFFFFFFF共4GB位址空間範圍,這4GB的位址空間範圍包括以下幾種存儲空間:裝置空間、内部高速SRAM空間、内部mini cache空間、低端中斷向量空間、高端中斷向量空間、RAM記憶體空間(系統的記憶體空間)、ROM空間。

記憶體頁(PAGE):

            Linux一般以頁為機關管理實體記憶體的,一般頁的大小問4kb,對于頁可以做到多小還是和處理器有關的。

記憶體區段(bank):

            一個BANK表示一塊連續的記憶體空間,對應于處理器的ram片選管腳連結的ram的記憶體空間。對于RAM在系統的起始位址和大小可以通過處理器寄存器設定的,如果處理器所連結所有RAM晶片都設定為首尾位址相連的話,Linux就可以用一個BANK來表示這片記憶體空間。

記憶體節點(node):

            其實就是一個或者多個BANK組成的集合,對于上述的1個BANK的話,就是隻有一個記憶體節點了。如果RAM的起始位址是固定的,不可設定的話,就有可能是記憶體空間不連續,這樣就需要幾個BANK了。如果對Linux配置了CONFIG_DISCONTIGMEM的話,即可以每個BANK可以對應不同的記憶體節點,或是多個BANK對應一個記憶體節點,不配置的話,全部都對應記憶體節點0。當多個BANK對應一個記憶體節點時,在上述的情況可能會有記憶體孔洞,這樣在啟動的時候就要産生頁幀位碼和struct page資料結構。這樣會影響到系統在啟動的時候獲得最大連續實體記憶體,但是啟動後就不會有任何影響,因為mem_init()會把這些頁幀位碼和struct page所占的空間釋放掉。

記憶體頁區(zone):

             每個記憶體節點可分為3個記憶體頁區,即DAM頁區、Normal頁區和HighMem頁區。每個頁區的含義如下:

             DAM頁區:可以進行DAM操作的RAM記憶體區域。

             Normal頁區:不可以進行DAM操作的RAM記憶體區域。

             HighMem頁區:屬于高端記憶體的區域,高端記憶體是指系統中的實體記憶體容量太大,其中超過一定域值的RAM記憶體頁區就是高端記憶體頁區。

空閑記憶體區域(free area):是記憶體頁區内連續2^order頁空閑記憶體組成的記憶體區域。

二.linux記憶體管理的任務為以下幾項:

1)規劃整個系統的實體和虛拟存儲空間布局。

2)為虛拟空間即線性位址空間建立頁表(虛拟位址到實體位址的映射關系)。

3)設定不同存儲空間的通路控制屬性,保護系統存儲空間不被非法通路。

4)記憶體配置設定和釋放。

三.linux中的實體和虛拟存儲空間布局:

linux運作在虛拟存儲空間的,由于系統支援MMU,則系統負責把遠遠小于4GB的實體記憶體根據不同的需要映射到整個4GB的虛拟記憶體空間中。首先我們來先說說他們的分布。

Linux實體記憶體空間一般分布:

首先它分為幾個node(記憶體節點,記憶體節點号最大不可以超過MAX_NUMNODES-1,節點号是從0号開始的),然後有的記憶體節點隻是包括一個BANK(記憶體區段),有的包括幾個BANK,有的幾個BANK裡面可能有孔洞,有的可能沒有。在這裡我們主要說明一下node0号。

node0号的起始位址就是系統RAM記憶體實體開始位址,接着就是一級映射符号表,接着就是編譯出核心本身所占的空間,接着就是逆序的每個node的頁幀位碼表。

系統RAM實體記憶體的開始位址就是node0号的開始位址,結束位置要看具體的RAM實體記憶體有多大。

一級映射符号表一般都是16kb,一般用swapper_pg_dir變量記錄它在實體記憶體空間的起始位址。

編譯出核心本身它的起始位址由_stext記錄,結束位址由_end記錄。

node的頁幀位碼表一共有多少,是由有多少個node決定的,而且存放的順序是逆序,就是說現實有最大的node号的頁幀位碼表開始存放的,一直到node0号的頁幀位碼表。如果不清楚這個表有什麼用途的話,我建議你上網查找一下。每個頁幀位碼表的起始位址是由node_bootmem_data[i].nod_bootmem_map變量來存放的。這個變量的結構體是struct bootmem_data(bootmem_data_t)

這樣,我們就把Linux實體記憶體空間分布說完了,現在接下來說說Linux系統虛拟記憶體空間一般分布。

       首先,核心空間和使用者空間合起來就是Linux線性位址空間(Linux虛拟位址空間)。

       核心空間大小為1GB,位址範圍是:0xC0000000~0xFFFFFFFF。處理器的最進階别的超級使用者模式下的代碼和資料都是放在這裡。工作在核心态的程序可以共享這部分記憶體。對于核心空間,我們要把它展開,它分為:核心邏輯位址空間和高端線性位址空間。

       (1)核心邏輯位址空間,位址範圍是:PAGE_OFFSET~high_memory。這個線性位址空間是系統實體記憶體映射區。這裡說的 系統實體記憶體其實就是我們剛才上面說的系統ram實體記憶體。對于要映射多少,這個和實際的記憶體有關,有時是全部,有時可能隻是部分。有人會問,為什麼不全部映射呢?這樣不是浪費了!對于這個問題,我會在接下來說明的。這裡還要補充一下,其實你可以看出,系統ram實體記憶體的起始位址是從0x0開始的,而核心邏輯空間的起始位址是從PAGE_OFFSET開始的,再加上他們之間的映射關系式一一對應的,這樣說大小也是要一樣的。是以說對于MMU來說,轉換時就是加上或是減去一個PAGE_OFFSET的偏移量就可以了。

       在說高端線性位址空間之前,我要先介紹兩個概念:低端記憶體、高端記憶體。

       低端記憶體:其實就是說系統RAM實體記憶體被映射要核心邏輯位址空間那部分實際記憶體,這部分記憶體是在系統初始化的時候就映射好了,還建立了頁表,是永久的。

       高端記憶體:比低端記憶體高出的那部分記憶體就是高端記憶體,這部分記憶體沒有固定的一一映射的核心邏輯位址空間。在系統初始化的時候一不會負責映射,二不會為之建立頁表。隻有需要的時候,才會為配置設定的高端記憶體建立頁表,這樣才可以被使用。對于什麼時候開始才是高端記憶體,其實是存在一個閥值的,接下來我們再說說這個概念。

       閥值:0xC0000000~0xFFFFFFFF這個空間除了要映射系統記憶體空間外,還要映射處理器外設寄存器空間等I/O空間。當系統記憶體空間遠遠比1GB小的時候,這樣在其之上的high_memory~0xFFFFFFFF就有足夠的空間來個I/O空間映射了。當系統記憶體高于1GB的時候,就需要設定一個閥值,比如x86設定為896MB,這樣前896MB就會映射到核心邏輯位址空間,剩下的128MB就是用來映射高端記憶體和I/O空間。我們稱這段位高端線性位址空間。

       (2)高端線性位址空間,位址範圍是:high_memory~0xFFFFFFFF。關于這段位址空間的分布,我不打算細講。

四.為虛拟存儲空間建立頁表:

    其實在MMU中,我們已經知道有一級頁表和二級頁表之分了。其實對于Linux來說,我們是使用兩級頁表來實作位址映射的。其實就是考查一張表。現在,我們來讨論一下,這些表是怎麼建立的。

    一級頁表存放在虛拟位址swapper_pg_dir處,這張表裡的每一項對應4GB虛拟記憶體中1MB存儲空間,由于每一項占4位元組,一共需要4096項,這樣一張表的大小就是16KB。這張表的基址存放在swapper_pg_dir,表裡的每項的位址對應于swapper_pg_dir[i]每個元素單元裡面存放的内容。對于一個虛拟位址是固定對應這裡的某個元素,前提是它一定要屬于這個段。這樣一個數組指針其實是存放在struct mm_struct xxx_mm.pgd這個變量裡。xxx_mm這個變量一般是用來描述記憶體的。

    二級頁表是以兩端(2MB)空間所需的二級頁表容量大小為機關來建立和配置設定的,為什麼要以這個機關來建立呢?我現在來仔細說一下。

    首先,在ARM中swapper_pg_dir[i]每個單元對應的資料結構是pgd_t,這是一個8位元組的類型。換句話說,一個這樣的元素就對應兩個一級頁表中的兩個頁表項,也就是說對應兩張二級頁表。而在Linux中,每張表有兩份實作,一份是Linux定義的,一份是硬體定義的,兩個版本頁表的大小一樣,是以這裡要申請2*2*256*4=4KB大小空間。

    總的來說,我們現在知道一張一級和二級的表是需要多大的記憶體空間來存放的。我們現在來看看Linux是怎麼實作的。

    Linux在啟動開始的彙編代碼部分會調用_create_page_tables()彙編函數為系統的記憶體頭4段(4MB)建立一張映射表。這樣這頭4MB的系統記憶體就和核心邏輯位址空間一一對應上了。最前面的16KB、一級頁表16KB(這個就是我們上面說的)和核心本身(1MB),這樣還剩下大約2MB多的空間,接下來為每個記憶體節點的頁幀位碼表的建立,提供最大為1MB的空間,剩下來的1MB多的空間就是空閑下來的,有了這些空閑的空間,就可以供某些函數申請用。如果你想使用低端記憶體或是其他的一些存儲空間的話。先要建立我們上面二級頁表。要想建立一個二級頁表,就必須先申請用來存放二級頁表的空間。這就是為什麼剩下這些空閑空間的意義所在啦。

五.設定存儲空間的通路控制屬性:

    看過MMU文章,就不會對這個有任何陌生的感覺了,在這裡我不在細講這是怎麼回事。我這裡隻是大概說下Linux的ARM中對這16個域的通路控制是怎麼設定的。域3~15沒有通路權限,0~1為管理者通路權限,最後2是客戶通路權限。對于0~2域,Linux有再次細分了一次,就是在Linux裡面有4種域,它們的定義如下:

       #define DOMAIN_USER 0

       #define DOMAIN_KERNEL 1

       #define DOMAIN_TABLE 1

       #define DOMAIN_IO 2

很明顯,後面的數字表示這四個域分别屬于0~2域中的哪個域。這裡有點特殊的是,0号域不是管理模式了,而是客戶模式了。是以每次用的時候要把權限改變一下,一般都是調用函數modify_domain。DOMAIN_USER是用于使用者空間的域,DOMAIN_KERNEL是用于核心空間的域,DOMAIN_TABLE是用于頁表空間的域,DOMAIN_IO是用于I/O空間的域。

現在介紹完Linux特殊定義後的域,還知道他們的權限。這樣我們來說說,在早前我們提到的4GB存儲空間包括的那幾種空間分别是屬于哪些域的。

         裝置空間:DOMAIN_IO

         内部高速SRAM空間:DOMAIN_KERNEL

         内部mini cache 空間;DOMAIN_KERNEL

         低端中斷向量空間:DOMAIN_USER

         高端中斷向量空間:DOMAIN_USER

         RAM記憶體空間:DOMAIN_KERNEL

         ROM空間:DOMAIN_KERNEL

繼續閱讀