深入理解linux記憶體管理機制(一)通過本文,您即可以:
1. 儲存器硬體結構;
2.分段以及對應的組織方式;
3.分頁以及對應的組織方式。
注1:本文以linux核心2.6.32.59本版為例,其對應的**可以在
找到。
注2:本文所有的英文專有名詞都是我隨便翻譯的,請對照英文原文進行理解。
注3:推薦使用source insight進行原始碼分析。
記憶體組織
計算機記憶體屬於
隨機儲存器
(ram),目前pc機廣泛使用的是ddr
sdram,即「雙倍速率同步動態隨機儲存器」,其本質上仍然是由n bits*m kb個記憶體晶元組成的,比如如果我們需要8位64kb的記憶體,則我們就需要2*8=16塊4bits*8kb的記憶體塊。由於計算機通常是以
位元組(byte)進行資料交換的,所以對記憶體的位址編碼一般使用位元組,如上我們有64kb記憶體,則其位址編碼為0x0000~0xffff,稱為實體地址。對於32位機來說,由於其「
位址暫存器
(ar)」是32位,也就限制了其記憶體的最大定址範圍是2^32=4gb。
linux將實體地址按4kb的大小劃分成「幀(frame)」。為什麼是4kb?因為每乙個幀都需要用乙個c結構體來描述,稱之為「幀描述單元(frame discriptor)」,如果太小,幀描述單元顯然太多了,如果太大,那麼在記憶體分配時又會造成「
內碎片(inner
fragments)」。早些時候,計算機的記憶體址都是直接對映的,由於程式裡的位址是寫死的,這就意味著每段程式每次都只能對映對應的位址空間。這無論對程式設計者與系統都是相當大的負擔。linux使用「分段
」加「分頁
address)、線性位址(linear address)與實體地址(physics address)。
另外,linux支援眾多cpu架構,這裡只研究x86的,對應的源**為:.../x86/... 路徑。
linux中的分段
linux並不使用太多的分段,原因是某些risc機器對分段的支援不好。為此linux的分段都存在「全域性描述表(gdt)」中,gdt是乙個全域性desc_struct陣列(位於linux-2.6.32.59archx86includeasm),其結構如下:
#define gdt_entries 16
struct desc_struct gdt[gdt_entries];
struct desc_struct ;
struct ;
};
} __attribute__((packed));
所以我們可以看出,段描述結構體占8個位元組,至於裡面的a,b,那是老的方式,後來使用c++ struts的bit fields後更方便了。type型別由以下幾種:
enum ;
linux主要使用以下幾種段:
其它型別可以參見linux-2.6.32.59archx86includeasmsegment.h,裡面有非常詳細的說明。
它們都儲存在「全域性描述符表(gdt)」。linux本身並不使用「區域性描述符表(ldt)」,當乙個程序被建立時,其指向的是乙個預設的ldt,不過系統並不阻止程序建立它。也就是說乙個程序最多兩個段描述符:tss與ldt。由於segment selector為16位(為什麼只有16位,這個就是歷史原因了,由於x86在real mode下段位址只有20位,其中有效的就是16位,詳見:
x86
memory segmentation,但linux段內偏移位址高達32位,所以線性位址總共是48位),其中有效的索引位僅有13位,所以gdt的最大長度為2
13-1=8192,除去系統保留的12個,留給程序的只有8180個入口,那麼就意味linux程序的最大數為8180/2=4090。需要注意的是,程序在建立的時候並不會馬上建立自己的ldt,其指向的是gdt乙個預設的ldt,裡面的sd為null。只有在需要的時候程序才建立自己的ldt並把它放入gdt中。所以不管是ldt也好,tss也好,它們都存放在gdt裡面。而對於ucs與uds,所有的程序共享乙個。這樣位址空間不會重複嗎?不會,因為線性不是最終的實體地址,每個程序還有自己的頁表,所以最終對映到實體地址是不同的。
下面我們來看看段中位址是如何轉換的。假設我們需要訪問核心資料段的0x00124部分,由**知其gdt的入口為13,那麼其對應的記憶體位址=gdtr+13*8+0x00124,假設gptr為0x02000,則最終的結果為0x02228。gdtr是乙個暫存器,其為48位,用來儲存gdt的第乙個位元組線性位址與表限。
分頁
相對於分段來說,分頁更主流更流行一些。原因是其更靈活,其能把不同的線性位址對映到同乙個實體地址上,缺點是記憶體必須以頁大小的整數倍分配。按現在主流的4kb一頁來說,如果程式只申請100b的資料,那記憶體浪費還是相當的大。為此,linux使用了一種稱為slab的方法來解決這個問題,後面的文章會講到。
因為頁表本身也需要儲存空間,按每頁32b來算,對於4gb記憶體,每頁4kb,共有1m頁,則頁表的大小為32mb,這顯然不可以接受,所以後來出現了
多級頁表
這個概念。2023年後linux版本使用的是四級頁表:第一級叫「全域性目錄(page
global directory)「、第二級叫「頁上級目錄(page
upper directory)」、第**叫」頁中間目錄(page
middle derectory)」、第四級叫」頁面表(page
table entry)」,最後頁內偏移量「offset」.
cr3是乙個暫存器,它儲存「global dir」的位址。當程序切換發生時,它將被儲存在tss中,前面說過了tss段表是每個程序乙個。分頁在linux內使用的地方很多,特別是程序內的位址轉換。分頁有硬體支援的,特別是
旁路轉換緩衝(translation
lookaside buffer)的出現,使用即使使用**頁表的linux在地轉轉換中的實際效果也是非常好的。與段表所有的程序都共用乙個的是,每個程序都擁有自己的分頁。其實也正是因為所有程序都共享乙個段表,每個程序才必須有自己的頁表,否則相同的linear位址如何對映到不同的實體地址去?下面我們著重來研究一下linux系統中是如何表示分頁中所用到的資料結構的。
每個「幀」在linux中都是以乙個名為page(位於linux-2.6.32.59includelinuxmm_types.h)的結構體來儲存的。所有的頁被放在乙個型別為page名為mem_map的陣列中(位於linux-2.6.32.59mmmemory.c)。**如下(為了顯示方便,僅列出部分:
struct page ;
union ;
...
};
union ;
struct list_head lru; /* 指向最近被使用的頁的雙向鍊錶,cache相關*/
};
下面我們再來看看pgd頁表。每個程序的mm_struct->pgd(位於:linux-2.6.32.59includelinuxmm_types.h)指向自己的pgd:
struct mm_struct
可以看出pdg實際上是乙個pgd_t結構陣列,pgd_t在x86系統中就是乙個usinged long,其指向的就是下一級頁表的位址。就這樣找下去,直到找到對應的頁為止,再加上頁內偏移,就可以進行記憶體訪問了。
例如線性位址為:0x91220b01,如果pgd、pud、pmd以及pte均5位。頁內偏移12位,即頁大小
那麼這段記憶體的解析步驟是:
pgd號為24,查pgd[24]得到pud入口;
pud號為4,再查pud[4];
pmd號為36,再查pmd[36];
pte號為2,再查pte[2];
需要補充的是,並不是所有的記憶體都是使用「分頁」,在核心初始化的時候,有100mb記憶體的樣子是使用直接對映的,這是因為總是要先裝入分頁的初始化**才能進行頁表初始化。
總結:不知不覺也寫了不少了。這次我們介紹了作業系統最基本的記憶體管理概念「分段」與「分頁」在linux中的實現,可以看出其與通過的概念還是很接近的。這正證明了基礎知識的重要性。下一次我們將介紹linux的記憶體初始化過程,如頁表的建立與初始化。
------------------一些資源與參考-------------------
linux slub 分配器詳解:
page frame management:
linux memory management:
linux記憶體管理**:
linux記憶體之頁表:
深入理解Linux記憶體管理機制(一)
一 記憶體組織 計算機記憶體屬於隨機儲存器 ram 目前pc機廣泛使用的是ddr sdram,即 雙倍速率同步動態隨機儲存器 其本質上仍然是由n bits m kb個記憶體晶元組成的,比如如果我們需要8位64kb的記憶體,則我們就需要2 8 16塊4bits 8kb的記憶體塊。由於計算機通常是以位元...
深入理解Linux記憶體管理機制(一)
深入理解linux記憶體管理機制 一 通過本文,您即可以 1.儲存器硬體結構 2.分段以及對應的組織方式 3.分頁以及對應的組織方式 注1 本文以linux核心2.6.32.59本版為例,其對應的 可以在 找到。注2 本文所有的英文專有名詞都是我隨便翻譯的,請對照英文原文進行理解。注3 推薦使用so...
深入FDO 記憶體管理機制
呼叫某些fdo的函式,如建立方法,需要申請記憶體,而這些記憶體需要在適當的時機釋放,以免記憶體洩漏。fdo使用了引用計數的方式來實現管理物件的生命週期,每個物件都維護著乙個引用計數,只有當這個物件的引用計數變為0時,才會去釋放這個物件。所以,fdo中每個類都從fdoidisposable類繼承而來的...