深入理解Linux記憶體管理機制(一)

2021-06-13 14:59:58 字數 3748 閱讀 7101

一、記憶體組織

計算機記憶體屬於隨機儲存器(ram),目前pc機廣泛使用的是ddr

sdram,即「雙倍速率同步動態隨機儲存器」,其本質上仍然是由n bits*m kb個記憶體晶元組成的,比如如果我們需要8位64kb的記憶體,則我們就需要2*8=16塊4bits*8kb的記憶體塊。由於計算機通常是以位元組(byte)進行資料交換的,所以對記憶體的位址編碼一般使用位元組,如上我們有64kb記憶體,則其位址編碼為0×0000~0xffff,稱為實體地址。對於32位機來說,由於其「位址暫存器(ar)」是32位,也就限制了其記憶體的最大定址範圍是2^32=4gb。

另外,linux支援眾多cpu架構,這裡只研究x86的,對應的源**為:…/x86/… 路徑。

linux中的分段

linux並不使用太多的分段,原因是某些risc機器對分段的支援不好。為此linux的分段都存在「全域性描述表(gdt)」中,gdt是乙個全域性desc_struct陣列(位於linux-2.6.32.59\arch\x86\include\asm),其結構如下:

#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.59\arch\x86\include\asm\segment.h,裡面有非常詳細的說明。

它們都儲存在「全域性描述符表(gdt)」。linux本身並不使用「區域性描述符表(ldt)」,當乙個程序被建立時,其指向的是乙個預設的ldt,不過系統並不阻止程序建立它。也就是說乙個程序最多兩個段描述符:tss與ldt。由於segment selector為16位(為什麼只有16位,這個就是歷史原因了,由於x86在real mode下段位址只有20位,其中有效的就是16位,詳見:

x86memory 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,所有的程序共享乙個。這樣位址空間不會重複嗎?不會,因為線性不是最終的實體地址,每個程序還有自己的頁表,所以最終對映到實體地址是不同的。

下面我們來看看段中位址是如何轉換的。假設我們需要訪問核心資料段的0×00124部分,由**知其gdt的入口為13,那麼其對應的記憶體位址=gdtr+13*8+0×00124,假設gptr為0×02000,則最終的結果為0×02228。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」,如下圖:

程序通過資料結構mm_struct訪問pgd,最終找到物理頁面:

邏輯上把虛擬位址從高到低分成5部分,分別作為pgd、pud、pmd、pte的索引,以及頁面內偏移。對於cpu發出的虛擬位址,linux記憶體管理單元分如下五步完成從虛擬位址到實體地址的對映:

1) 用虛擬位址的最高乙個位段作為索引在pgd表中找到對應的表項,該表項指向乙個pud表的基位址。

2) 用虛擬位址中的第二個位段作為索引在pud表中找到對應的表項,該表項指向乙個pmd表的基位址。

3) 用虛擬位址中的第三個位段作為索引在pmd表中找到對應的表項,該表項指向乙個pte表的基位址。

4) 用虛擬位址中的第四個位段作為索引在pte表中找到對應的表項,該表項中存放的就是指向物理頁面的指標(實際上是頁幀號pfn和一些標誌位的組合體)。

5) 虛擬位址中的最後乙個位段是物理頁面的頁內偏移,將此偏移與物理頁面的起始位址相加便得到虛擬位址對應的實體地址。

後面分析核心**時,會用到下面列出的巨集:

n         pgdir_shift:虛擬位址中pgd索引位段的起始位址。

n         ptrs_per_pgd:pgd表中表項的數目。

n         pgdir_size:pgd中每個表項所代表的空間大小。

n         user_ptrs_per_pgd:需要使用多少個pgd表項才能代表使用者空間。

n         pud_shift:虛擬位址中pud索引位段的起始位址。

n         ptrs_per_pud:pud表中表項的數目。

n         pud_size:每個pud表項所代表的空間大小。對於二級對映的架構而言,pud_size即pgdir_size。

n         pmd_shift:虛擬位址中pmd索引位段的起始位址。

n         ptrs_per_pmd:pmd表中表項的數目。

n         pmd_size:每個pmd表項所代表的空間大小。對於二級對映的架構而言,pmd_size即pgdir_size。

n         page_shift:虛擬位址中pte索引位段的起始位址。決定了頁面的大小。

n         page_size:頁面大小,1 << page_shift。通常為4kb。

n         ptrs_per_pte:pte表中表項的數目。

例如線性位址為:0x91220b01,如下圖,如果pgd、pud、pmd以及pte均5位。頁內偏移12位,即頁大小4kb。

那麼這段記憶體的解析步驟是:

pgd號為24,查pgd[24]得到pud入口;

pud號為4,再查pud[4];

pmd號為36,再查pmd[36];

pte號為2,再查pte[2];

如果最終幀位址為a:那麼最後的實體地址就是a+0×0301

需要補充的是,並不是所有的記憶體都是使用「分頁」,在核心初始化的時候,有100mb記憶體的樣子是使用直接對映的,這是因為總是要先裝入分頁的初始化**才能進行頁表初始化。

——————一些資源與參考——————-

linux slub 分配器詳解:

page frame management:

linux memory management:

linux記憶體管理**

:linux記憶體之頁表:

深入理解Linux記憶體管理機制(一)

深入理解linux記憶體管理機制 一 通過本文,您即可以 1.儲存器硬體結構 2.分段以及對應的組織方式 3.分頁以及對應的組織方式。注1 本文以linux核心2.6.32.59本版為例,其對應的 可以在 找到。注2 本文所有的英文專有名詞都是我隨便翻譯的,請對照英文原文進行理解。注3 推薦使用so...

深入理解Linux記憶體管理機制(一)

深入理解linux記憶體管理機制 一 通過本文,您即可以 1.儲存器硬體結構 2.分段以及對應的組織方式 3.分頁以及對應的組織方式 注1 本文以linux核心2.6.32.59本版為例,其對應的 可以在 找到。注2 本文所有的英文專有名詞都是我隨便翻譯的,請對照英文原文進行理解。注3 推薦使用so...

深入FDO 記憶體管理機制

呼叫某些fdo的函式,如建立方法,需要申請記憶體,而這些記憶體需要在適當的時機釋放,以免記憶體洩漏。fdo使用了引用計數的方式來實現管理物件的生命週期,每個物件都維護著乙個引用計數,只有當這個物件的引用計數變為0時,才會去釋放這個物件。所以,fdo中每個類都從fdoidisposable類繼承而來的...