1. 程式人生 > >linux段頁式記憶體管理技術

linux段頁式記憶體管理技術

一、概述

1.虛擬地址空間

記憶體是通過指標定址的,因而CPU的字長決定了CPU所能管理的地址空間的大小,該地址空間就被稱為虛擬地址空間,因此32位CPU的虛擬地址空間大小為4G,這和實際的實體記憶體數量無關。
Linux核心將虛擬地址空間分成了兩部分:
  • 一部分是使用者程序可用的,這部分地址是地址空間的低地址部分,從0到TASK_SIZE,稱為使用者空間
  • 一部分是由核心保留使用的,這部分地址是地址空間的高地址部分,從KERNELBASE到結束,稱為核心空間
與之相關的一些巨集:
  1. KERNELBASE:核心虛擬地址空間的起始地址,一般和PAGE_OFFSET相同,但是也可能不同
  2. PAGE_OFFSET:核心虛擬地址空間中低端記憶體的起始地址
  3. PHYSICAL_START:核心實體地址的起始地址
  4. MEMORY_START:核心低端記憶體的物理起始地址
使用者程序可用的部分在程序切換時會發生改變,但是由核心保留使用的部分在程序切換時是不變的。在32位系統上,兩部分的典型劃分比為3:1(該比例可修改),即4G虛擬地址空間中的3G是使用者程序可訪問的,而另外1G是保留給核心使用的,在這種劃分下使用者程序可用的虛擬地址空間是0x00000000-0xbfffffff,核心的虛擬地址空間是0xc0000000-0xffffffff。
不同的程序使用不同的使用者空間可以使得不同程序的使用者空間部分相互隔離,從而保護程序的使用者空間部分。
核心空間的保護是通過CPU的特權等級實現的,所有現代CPU都提供了多個特權等級,每個特權等級可以獲得的許可權是不同的,當CPU處在某個許可權等級時就只能執行符合這個等級的許可權限制的操作。Linux使用了兩個許可權等級,分別對應於核心許可權和使用者許可權,並且給屬於核心的記憶體空間添加了許可權限制,使得只有處於核心許可權等級時CPU才能訪問這些記憶體區域,這就將核心空間也保護了起來。

2.實體地址到虛擬地址的對映

可用的實體記憶體會被對映到核心虛擬地址空間中。在32位系統中,核心會將一部分實體記憶體直接對映到核心的虛擬地址空間中,如果訪問記憶體時所使用的虛擬地址與核心虛擬地址起始值的偏移量不超過該部分記憶體的大小,則該虛擬地址會被直接關聯到物理頁幀;否則就必須藉助”高階記憶體“來訪問,因此也可以看出之所以使用“高階記憶體”是因為CPU可定址的虛擬地址可能小於實際的實體記憶體,因而不得不借助其它機制(“高階記憶體”)來訪問所有的記憶體。在IA-32系統上,這部分空間大小為896M。
64位系統不使用高階記憶體,這是因為64位的系統理論上可定址的地址空間遠大於實際的實體記憶體(至少現在是如此),因而就不必藉助“高階記憶體”了。而對於使用者程序來說,由於它的所有記憶體訪問都通過頁表進行,不會直接進行,因而對使用者程序來說也不存在高階記憶體之說。

高階記憶體由32位架構的核心使用,在32位架構的核心中,要使用高階記憶體必須首先使用kmap將高階記憶體對映進核心的虛擬地址空間。

3.記憶體型別

從硬體角度來說存在兩種不同型別的機器,分別用不同的方式來管理記憶體。
  1. UMA(uniform memory access):一致記憶體訪問機器,它將可用記憶體以連續的方式組織起來。SMP系統中,每個CPU都可以以同樣的速度訪問記憶體。
  2. NUMA(non-uniform memory access):非一致記憶體訪問機器總是多處理器機器。系統的各個CPU都有本地記憶體,可以支援快速訪問。系統中的所有處理器都通過匯流排連線起來,進而可以訪問其它CPU的本地記憶體,但是不如訪問本地記憶體快。
 

lnux中如果要支援NUMA系統,則需要開啟CONFIG_NUMA選項。

二、記憶體組織

linux核心對一致和不一致的記憶體訪問系統使用了同樣的資料結構,因此對於不同的記憶體佈局,記憶體的管理演算法幾乎沒有區別。對於UMA系統,將其看作只有一個NUMA節點的NUMA系統,即將其看成NUMA的特例。這樣就將簡化了記憶體管理的其它部分,其它部分都可以認為它們是在處理NUMA系統。

1.基本概念和相關資料結構

linux引入了一個概念稱為node,一個node對應一個記憶體bank,對於UMA系統,只有一個node。其對應的資料結構為“struct pglist_data”。
對於NUMA系統來講, 整個系統的記憶體由一個名為node_data 的struct pglist_data(page_data_t) 指標陣列來管理。NUMA系統的記憶體劃分如圖所示:
  每個node又被分成多個zone,每個zone對應一片記憶體區域。核心引入了列舉常量 zone_type 來描述zone的型別:
  1. <mmzone.h>  
  2. enum zone_type {  
  3. #ifdef CONFIG_ZONE_DMA
  4. ZONE_DMA,  
  5. #endif
  6. #ifdef CONFIG_ZONE_DMA32
  7. ZONE_DMA32,  
  8. #endif
  9. ZONE_NORMAL,  
  10. #ifdef CONFIG_HIGHMEM
  11. ZONE_HIGHMEM,  
  12. #endif
  13. ZONE_MOVABLE,  
  14. MAX_NR_ZONES  
  15. };  
它們之間的用途是不一樣的:
  • ZONE_DMA:可用作DMA的記憶體區域。該型別的記憶體區域在實體記憶體的低端,主要是ISA裝置只能用低端的地址做DMA操作。
  • ZONE_NORMAL:直接被核心直接對映到自己的虛擬地址空間的地址。
  • ZONE_HIGHMEM:不能被直接對映到核心的虛擬地址空間的地址。
  • ZONE_MOVABLE:偽zone,在防止實體記憶體碎片機制中使用
  • MAX_NR_ZONES:結束標記
很顯然根據核心配置項的不同,zone的型別是有變化的。每個zone都和一個數組關聯在一起,該陣列用於組織管理屬於該zone的實體記憶體頁。
zone用資料結構struct zone來表示。
所有的node都被儲存在一個連結串列中。在使用時,核心總是嘗試從與程序所執行的CPU所關聯的NUMA節點申請記憶體。這是就要用到備用列表,每個節點都通過struct zonelist提供了備用列表,該列表包含了其它節點,可用於代替本節點進行記憶體分配,其順序代表了分配的優先順序,越靠前優先順序越高。

2.頁(Page)

1.頁概念

核心使用struct page作為基本單位來管理實體記憶體,在核心看來,所有的RAM都被劃分成了固定長度的頁幀。每一個頁幀包含了一個頁,也就是說一個頁幀的長度和一個頁的長度相同。頁幀是主存的一部分,是一個儲存區域。頁和頁幀的區別在於,頁是抽象的資料結構,可以存放在任意地方,而頁幀是真實的儲存區域。
struct page包含了跟蹤一個物理頁幀當前被用於什麼的有資訊。比如頁面計數,標誌等等。

2.對映頁面到zone

核心使用struct page的flags中的欄位來儲存頁所屬於的zone以及node。這是通過set_page_zone和set_page_node,這兩個函式由函式set_page_links呼叫。

三、頁表

1.頁表機制

CPU管理虛擬地址,因而實體地址需要對映到虛擬地址才能給CPU使用。用於將虛擬地址空間對映到實體地址空間的資料結構稱為頁表。
在使用4k大小頁的情況下,4k地址空間需要2的20次方個頁表項。即便每個頁表項大小為4位元組也需要4M記憶體,而每個程序都需要有自己的頁表,這就成了一個極大的記憶體開銷。而且在大多數情況下,虛擬地址空間的大部分割槽域都是沒有被使用的,因而沒必要為虛擬地址空間中的每個頁都分配管理結構,因而實際中採用的是如下方案:
  • 使用多級頁表,每個線性地址被看為形如“頁目錄表+頁目錄表+...+頁目錄表+頁表+頁內偏移”的形式,每個位元組按照其含義被用於在相應的表中查詢資料,最終找到頁表。
  • 程序的頁表只包含了它所使用的地址空間。程序不使用的地址空間不需要加入程序的頁表。
  • 只有在程序實際需要一個頁表時才會給該頁分配RAM,而不是在一開始就為程序的所有頁都分配空間。
頁表中包含了關於該頁的資訊,例如是否存在於主存中,是否是“髒”的,訪問所需許可權等級,讀寫標誌,cache策略等等。核心的頁表儲存在全域性變數swapper_pg_dir中,應用程序的頁表儲存在task_struct->mm->pgd中,在應用程序切換時,會切換程序的頁表(schedule-->__schedule-->context_switch-->switch_mm-->switch_mmu_context-->local_flush_tlb_mm)。

linux中採用了4級分頁模型。如下:

PGD PUD PMD PTE OFFSET
雖然採用了4級模型,但是:
  • 對於32位且未使能實體地址擴充套件的系統,使用二級頁表。Linux的做法是讓頁上級目錄表和頁中間目錄表所包含的位元數目為0,讓頁全域性目錄表的位元數目包含除了頁表和偏移量之外的所有位元,從而取消這兩級目錄。同時為了讓程式碼可以同時執行在32位元環境和64位元環境,linux保留了這兩級目錄在指標序列中的位置,做法是將這兩級目錄所包含的表項數設定為1(這裡需要注意的是即便只有一個位元,也可以表示兩個項,因此需要此設定)。
  • 對於32且使能了實體地址擴充套件的系統,使用三級頁表。
  • 對於64位系統,取決於硬體對線性地址位的劃分。
在linux中,每個程序都有自己的頁全域性目錄表(PGD),以及自己的頁表集。當發生程序切換時,linux會完成頁表的切換。
使用該方案後,每個虛擬地址都劃分為相應的位元分組,其中PGD用於索引每個程序所專有的頁全域性表,以找到PUD,PUD用於索引程序的頁上級目錄表,以找到PMD依次類推直到找到PTE。PTE即頁表陣列,該表的表項包含了指向頁幀的指標以及頁的訪問控制相關的資訊,比如許可權,是否在主存中,是否包含“髒”資料等等,OFFSET用做表內偏移。
使用該機制後,虛擬地址空間中不存在的記憶體區域對應的PUD,PMD,PTE將不被建立,這就節省了地址空間。

但是使用該機制後每次定址都需要多次查表,才能找到對應的實體地址,因而降低了速遞,CPU使用快取記憶體和TLB來加速定址過程。在訪問記憶體時,如果虛擬地址對應的TLB存在,也就是TLB 命中了,則直接訪問,否則就要使用相關的頁表項更新TLB(此時可能需要建立新的頁表項)然後再繼續進行訪問。下圖是一個CPU的虛擬地址到實地址的轉換過程:


當被訪問的地址不存在對應的TLB表項時,就會產生TLB中斷。在TLB中斷中,會:

  1. 首先查詢訪問地址對應的頁表,如果找不到對應的頁表,就會生成相應的頁表項(powerpc通過呼叫讀寫異常的處理函式完成該過程)。

  2. 使用PTE的內容更新TLB。

在TLB的內容更新完後,仍可能產生讀寫異常(也就是通常說的page fault),因為頁表項雖然存在,但是其內容可能是非法的(比如頁表並不在記憶體中),。

2.x86架構中的頁

1.地址空間

當使用x86時,必須區分以下三種不同的地址:
  • 邏輯地址:機器語言指令仍用這種地址指定一個運算元的地址或一條指令的地址。這種定址方式在Intel的分段結構中表現得尤為具體,它使得MS-DOS或Windows程式設計師把程式分為若干段。每個邏輯地址都由一個段和偏移量組成。
  • 線性地址:線性地址是一個32位的無符號整數,可以表達高達2的32次方(4GB)的地址。通常用16進製表示線性地址,其取值範圍為0x00000000~0xffffffff。
  • 實體地址:也就是記憶體單元的實際地址,用於晶片級記憶體單元定址。實體地址也由32位無符號整數表示。
X86中的MMU包含兩個部件,一個是分段部件,一個是分頁部件,分段部件(段機制)把一個邏輯地址轉換為線性地址;接著,分頁部件(分頁機制)把一個線性地址轉換為實體地址。轉化過程如圖所示:  

2.分段

1)分段機制

在x86段機制中,邏輯地址由兩部分組成,即段部分(選擇符)及偏移部分。
段是形成邏輯地址到線性地址轉換的基礎。如果我們把段看成一個物件的話,那麼對它的描述如下:
  1. 段的基地址(Base Address):線上性地址空間中段的起始地址。
  2. 段的界限(Limit):表示在邏輯地址中,段內可以使用的最大偏移量。
  3. 段的屬性(Attribute): 表示段的特性。例如,該段是否可被讀出或寫入,或者該段是否作為一個程式來執行,以及段的特權級等等。
段的界限定義邏輯地址空間中段的大小。段內在偏移量從0到limit範圍內的邏輯地址,對應於從Base到Base+Limit範圍內的線性地址。在一個段內,偏移量大於段界限的邏輯地址將沒有意義,使用這樣的邏輯地址,系統將產生異常。另外,如果要對一個段進行訪問,系統會根據段的屬性檢查訪問者是否具有訪問許可權,如果沒有,則產生異常。例如,在80386中,如果要在只讀段中進行寫入,80386將根據該段的屬性檢測到這是一種違規操作,則產生異常。
下圖表示一個段如何從邏輯地址空間,重新定位到線性地址空間。圖的左側表示邏輯地址空間,定義了A,B及C三個段,段容量分別為LimitA、LimitB及LimitC。圖中虛線把邏輯地址空間中的段A、B及C與線性地址空間區域連線起來表示了這種轉換。
  段的基地址、界限及保護屬性儲存在段的描述符表中,在虛擬—線性地址轉換過程中要對描述符進行訪問。段描述符又儲存在儲存器的段描述符表中,該描述符表是段描述符的一個數組。簡單的說段描述符表裡儲存了段描述符,而段描述符又包含了硬體進行邏輯地址到線性地址轉換所需的所有資訊。
每個段描述符都定義了線性地址空間中的一段地址,它的屬性以及它和邏輯地址空間之間的對映關係,實際上是如何從邏輯地址空間對映到線性地址空間。

2)linux中的段

各種段描述符都存放於段描述符表中,要麼在GDT中,要麼在LDT中。
描述符表(即段表)定義了386系統的所有段的情況。所有的描述符表本身都佔據一個位元組為8的倍數的儲存器空間,空間大小在8個位元組(至少含一個描述符)到64K位元組(至多含8K)個描述符之間。
  1. 全域性描述符表(GDT):全域性描述符表GDT(Global Descriptor Table),包含著系統中所有任務都共用的那些段的描述符。
  2. 區域性描述符表(LDT):區域性描述符表LDT(local Descriptor Table),包含了與一個給定任務有關的描述符,每個任務各自有一個的LDT。有了LDT,就可以使給定任務的程式碼、資料與別的任務相隔離。
每一個任務的區域性描述符表LDT本身也用一個描述符來表示,稱為LDT描述符,它包含了有關區域性描述符表的資訊,被放在全域性描述符表GDT中。
但是linux很少使用分段機制,這是因為,分段和分頁都能用於將實體地址劃分為小的地址片段的功能,因而它們是相互冗餘的。分段可以為不同的程序分配不同的線性地址空間,而分頁可以將相同的線性地址空間對映到不同的實體地址空間。linux採用了分頁機制,原因是:
  1. 如果所有的程序都使用相同的線性地址空間,記憶體管理更簡單
  2. 很多其他架構的CPU對分段的支援很有限
在linux中,所有執行在使用者模式的程序都使用相同的指令和資料段,因此這兩個段也被成為使用者資料段和使用者指令段。類似的,核心使用自己的核心資料段和核心資料段。這幾個段分別用巨集_ _USER_CS,_ _USER_DS,_ _KERNEL_CS, and_ _KERNEL_DS定義。這些段都從0開始,並且大小都相同,因而linux中,線性地址和邏輯地址是相同的,而且核心和使用者程序都可以使用相同的邏輯地址,邏輯地址也就是虛擬地址,這就和其它架構統一起來了。
單處理器系統只有一個GDT,而多處理器系統中每個CPU都有一個GDT,GDT存放在cpu_gdt_table中GDT包含了使用者資料段,使用者指令段,核心資料段核心指令段以及一些其他段的資訊。
絕大多數的linux使用者程式並不使用LDT,核心定義了一個預設的LDT給大多數程序共享。它存放於default_ldt中。如果應用程式需要建立自己的區域性描述附表,可以通過modify_ldt系統呼叫來實現。使用該系統呼叫建立的LDT需要自己的段。應用程式也可以通過modify_ldt來建立自己的段。

四、記憶體管理初始化 

1.初始化流程

記憶體初始化關鍵是page_data_t資料結構以及其下級資料結構(zone,page)的初始化。
巨集NODE_DATA用於獲取指定節點對應的page_data_t,在多節點系統中,節點資料結構為struct pglist_data *node_data[];該巨集獲取對應節點所對應的資料結構,如果是單節點系統,節點的資料結構為struct pglist_data contig_page_data;該巨集直接返回它。

1.初始化程式碼流程

系統啟動程式碼中與記憶體管理相關的初始化程式碼如圖:
  其功能分別為:
  • setup_arch:架構相關的初始化,其中包括了記憶體管理中與架構相關部分的初始化。boot分配器在這個時候被初始化。
  • setup_per_cpu_areas:SMP中,該函式初始化原始碼中靜態定義的每CPU變數,該類變數對系統中每一個CPU都一個副本。此類變數儲存在核心二進位制影響的一個獨立的段中。
  • build_all_zonelists:建立節點和zone的資料結構
  • mem_init:初始化記憶體分配器
  • setup_per_cpu_pageset:遍歷系統中所有的zone,對於每一個zone為所有的CPU分配pageset(冷熱頁快取)並進行初始化,在這個函式被呼叫之前,只有boot pagesets可用。

2.節點和zone的初始化

build_all_zonelists會遍歷系統中所有的節點,併為每個節點的記憶體域生成資料結構。它最終會使用節點資料結構呼叫build_zonelists,該函式會在該節點和系統中其它節點的記憶體之間建立一種距離關係,距離表達的是從其它節點分配的代價,因而距離越大,分配代價也越大;之後的記憶體分配會依據這種距離進行,優先選擇本地的,如果本地的不可用,則按照距離從近到遠來分配,直到成功或者所有的都失敗。
在一個節點的記憶體域中:
  1. 高階記憶體被看做是最廉價的,因為核心不依賴於高階記憶體,它被耗盡不會對系統有不良影響
  2. DMA看做是最昂貴的,因為它有特殊用途,它用於和外設互動資料
  3. 普通記憶體介於兩者之間,因為核心有些部分是依賴於普通記憶體的,所以它耗盡對系統會有影響
當分配記憶體時,假設指定的記憶體區域的昂貴程度為A,則分配過程為:
  1. 首先嚐試從本節點分配,並且是按照昂貴程度遞增的順序從A開始嘗試,直到最昂貴的區域
  2. 如果從本節點分配失敗,則按照距離關係依次檢查其它幾點,在檢查每個節點時,仍是按照昂貴程度遞增的順序從A開始嘗試,直到最昂貴的區域

2.特定於體系結構的設定

1.核心在記憶體中的佈局

在啟動裝載器將核心複製到記憶體,並且初始化程式碼的彙編部分執行完後,記憶體佈局如圖所示:

這是一種預設佈局,也存在一些例外:
  • PHYSICAL_START可用於配置修改核心在記憶體中的位置。
  • 核心可以被編譯為可重定位二進位制程式,此時由啟動裝載器決定核心的位置。
預設情況下,核心安裝在RAM中從實體地址0x00100000開始的地方。也就是第2M開始的那個。沒有安裝在第1M地址空間開始的地方的原因:
  • 頁幀0由BIOS使用,存在上電自檢(POST)期間檢查到的系統硬體配置。
  • 實體地址從0x000a0000到0x000fffff的範圍通常保留給BIOS程式使用
  • 第一個MB內的其它頁幀可能由特定計算機模型保留
從_edata到_end之間的初始化資料部分所佔用的記憶體在初始化完成後有些是不再需要的,可以回收利用,可以控制哪些部分可以回收,哪些部分不能回收。
核心佔用的記憶體分為幾段,其邊界儲存在變數中,可以通過System.map檢視相關的資訊,在系統啟動後也可以通過/proc/iomem檢視相關的資訊。

2.初始化步驟

在start_kernel,在其中會呼叫setup_arch來進行架構相關的初始化。setup_arch會完成啟動分配器的初始化以及各個記憶體域的初始化(paging_init)。paging_init最終會呼叫free_area_init_node這是個架構無關的函式,它會完成節點以及zone的資料結構的初始化。

3.分頁機制初始化

Linux核心將虛擬地址空間分成了兩部分:使用者空間和核心空間。使用者程序可用的部分在程序切換時會發生改變,但是由核心保留使用的部分在程序切換時是不變的。在32位系統上,兩部分的典型劃分比為3:1(該比例可修改),即4G虛擬地址空間中的3G是使用者程序可訪問的,而另外1G是保留給核心使用的。
32位系統中,核心地址空間又被分為幾部分,其圖示如下:

3.1 直接對映

其中第一部分用於將一部分實體記憶體直接對映到核心的虛擬地址空間中,如果訪問記憶體時所使用的虛擬地址與核心虛擬地址起始值的偏移量不超過該部分記憶體的大小,則該虛擬地址會被直接關聯到物理頁幀;否則就必須藉助”高階記憶體“來訪問,在IA-32系統上,這部分空間大小為896M。
對於直接對映部分的記憶體,核心提供了兩個巨集:

  • __pa(vaddr):用於返回與虛擬地址vaddr相對應的實體地址。
  • __va(paddr):用於返回和實體地址paddr相對應的虛擬地址。
剩餘部分被核心用作其它用途:
  1. 虛擬地址中連續,但是實體地址不連續的記憶體區域可以從VMALLO區域分配。該機制通常用於使用者程序,核心自己會盡量嘗試使用連續的實體地址。當然,當直接對映部分不能滿足需求時,核心也會使用該區域。在ppc32中ioremap就使用了該區域。
  2. 持久對映區域用於將高階記憶體中的非持久頁對映到核心中。
  3. 固定對映用於與實體地址空間中的固定頁關聯的虛擬地址頁,但是實體地址頁即頁幀可以自由選擇。
記憶體的各個區域邊界由圖中所示的常數定義。high_memory定義了直接對映區域的邊界。
系統中定義了與頁相關的一些常量:
  • num_physpages:最高可用頁幀的頁幀號
  • totalram_pages:可用頁幀的總數目
  • min_low_pfn:RAM中在核心映像之後的第一個可用的頁幀號
  • max_pfn:最後一個可用的頁幀號
  • max_low_pfn:被核心直接對映的最後一個頁幀的頁幀號(低端記憶體中)
  • totalhigh_pages:沒有被核心直接對映的頁幀的總數(高階記憶體中)
在直接對映的記憶體區域和用於vmalloc的記憶體區域之間有一個大小為VMALLOC_OFFSET的缺口,它用於對核心進行地址保護,防止核心進行越界訪問(越過了直接對映區域)。

3.2 vmalloc區

vmalloc區域的起始位置取決於high_memory和VMALLOC_OFFSET。而其結束位置則取決於是否啟用了高階記憶體支援。如果沒有啟用高階記憶體支援,就不需要持久對映區域,因為所有記憶體都可以直接對映。

3.3 持久對映區

持久對映頁則開始於PKMAP_BASE,其大小由LAST_PKMAP表示有多少個頁。

3.4 固定對映區

固定對映開始於FIXADDR_START結束於FIXADDR_END。這部分割槽域指向實體記憶體的隨機位置。在該對映中,虛擬地址和實體地址之間的關聯是可以自由定義的,但是定義後就不能更改。該區域一直延伸到虛擬地址空間的頂端。

固定對映的優勢在於編譯時,對該類地址的處理類似於常數,核心一旦啟動即為它分配了實體地址。對此類地址的引用比普通指標要快。在上下文切換期間,核心不會將對應於固定地址對映的TLB刷新出去,因此對這類地址的訪問總是通過快取記憶體。
對於每一個固定地址,都必須建立一個常數並新增到稱為fixed_addresses的列舉列表裡。核心提供了virt_to_fix和fix_to_virt用於虛擬地址和固定地址常數之間的轉換。

set_fixmap用於建立固定地址常量和物理頁之間的對應關係。

3.5 冷熱頁

free_area_init_node最終會調到zone_pcp_init,它會為該zone計算一個batch值。而setup_per_cpu_pageset則會完成冷熱快取的初始化。

3.啟動過程中的記憶體管理

bootmem分配器用於核心在啟動過程中分配和記憶體。這是一個很簡單的最先適配的分配器。它使用點陣圖來管理頁面,位元1表示頁忙,0表示空閒。需要分配記憶體時就掃描點陣圖,直到找到第一個能夠滿足需求的記憶體區域。

1.資料結構

核心為每個節點都分配了一個struct bootmem_data結構的例項用來管理該node的記憶體。

2.初始化

在不同的架構下初始化的程式碼不盡相同,但是都是在paging_int中被呼叫。

3.分配器介面

alloc_bootmem*用於分配記憶體free_bootmem*用於釋放記憶體

4.停用bootmem分配器

當slab系統完成初始化,能夠承擔記憶體分配工作時,需要停掉該分配器,這是通過free_all_bootmem(UMA系統)或free_all_bootmem_node(NUMA系統)來完成的

5.釋放初始化資料

核心提供了兩個屬性__init用於標記初始化函式,__initdata用於標記初始化資料,這意味著這個函式/資料在初始化完成後其記憶體就不需了,可以進行回收利用。

4. 核心頁表的初始化

以powerpc為例,核心頁表的初始化由MMU_init來完成,它在start_kernel之前被呼叫:

MMU_init->mapin_ram->__mapin_ram_chunk->map_page,

map_page的程式碼如下:

  1. int map_page(unsigned long va, phys_addr_t pa, int flags)  
  2. {  
  3.         pmd_t *pd;  
  4.         pte_t *pg;  
  5.         int err = -ENOMEM;  
  6.         /* Use upper 10 bits of VA to index the first level map */
  7.         pd = pmd_offset(pud_offset(pgd_offset_k(va), va), va);  
  8.         /* Use middle 10 bits of VA to index the second-level map */
  9.         pg = pte_alloc_kernel(pd, va);  
  10.         if (pg != 0) {  
  11.                 err = 0;  
  12.                 /* The PTE should never be already set nor present in the 
  13.                  * hash table 
  14.                  */
  15.                 BUG_ON((pte_val(*pg) & (_PAGE_PRESENT | _PAGE_HASHPTE)) &&  
  16.                        flags);  
  17.                 set_pte_at(&init_mm, va, pg, pfn_pte(pa >> PAGE_SHIFT,  
  18.                                                      __pgprot(flags)));  
  19.         }  
  20.         return err;  
  21. }  

再看下init_mm的相關定義:
  1. struct mm_struct init_mm = {   
  2.         .mm_rb          = RB_ROOT,  
  3.         .pgd            = swapper_pg_dir,  
  4.         .mm_users       = ATOMIC_INIT(2),  
  5.         .mm_count       = ATOMIC_INIT(1),  
  6.         .mmap_sem       = __RWSEM_INITIALIZER(init_mm.mmap_sem),  
  7.         .page_table_lock =  __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),  
  8.         .mmlist         = LIST_HEAD_INIT(init_mm.mmlist),  
  9.         INIT_MM_CONTEXT(init_mm)  
  10. };  

因此可見,kernel的頁表是儲存在swapper_pg_dir中的。它是init_task的active_mm:

  1. 相關推薦

    linux記憶體管理技術

    一、概述 1.虛擬地址空間 記憶體是通過指標定址的,因而CPU的字長決定了CPU所能管理的地址空間的大小,該地址空間就被稱為虛擬地址空間,因此32位CPU的虛擬地址空間大小為4G,這和實際的實體記憶體數量無關。Linux核心將虛擬地址空間分成了兩部分:一部分是使用者程序

    記憶體管理中,邏輯地址,線性地址,實體地址的區別

    虛擬記憶體(Virtual Memory) 是指計算機呈現出要比實際擁有的記憶體大得多的記憶體量。因此它允許程式設計師編制並執行比實際系統擁有的記憶體大得多的程式。這使得許多大型專案也能夠在具有有限記憶體資源的系統上實現。一個很恰當的比喻是:你不需要很長的軌道就可以讓一列火車從上海開到北京。你只需要足夠長的鐵

    段式、記憶體管理--學習《Linux核心原始碼情景分析》第一章

        不得不說《Linux核心原始碼情景分析》這本書被那麼多人當作經典是有原因的,這裡只是該書的筆記遠不及毛老師描述的清楚。     對第一章做一個總結。這一章主要講解段式和頁式記憶體管理,當然還有一些其他東西。 Linux核心版本號的格式

    儲存管理方式

    基本分頁儲存管理方式 (1)頁面與頁表:頁面將一個程序的邏輯地址空間分成若干個大小相等的片,分頁地址中頁號和頁內地址的計算P=INT[A/L],d=[A] MOD L;頁表:系統為每個程序建立了一張頁面映像表簡稱頁表; (2)地址變換機構:實現從邏輯地址到實體地址的轉換  

    作業系統 第四章 3 分、分段、儲存管理 +作業題

    一、分頁儲存管理方式 1、(物理)塊:記憶體劃分成多個小單元,每個單元K大小         頁面:作業也按K單位大小劃分成片         物理劃分塊的大小 = 邏輯劃分的頁的大小

    分段,分儲存管理

    一.  分頁儲存管理 1.基本思想 使用者程式的地址空間被劃分成若干固定大小的區域,稱為“頁”,相應地,記憶體空間分成若干個物理塊,頁和塊的大小相等。可將使用者程式的任一頁放在記憶體的任一塊中,實現了離散分配。 1)      等分記憶體 頁式儲存管理將記憶體空間

    記憶體管理

    基本原理     1.等分記憶體     頁式儲存管理將記憶體空間劃分成等長的若干區域,每個區域的大小一般取2的整數冪,稱為一個物理頁面有時稱為塊。記憶體的所有物理頁面從0開始編號,稱作物理頁號。     2.邏輯地址     系統將程式的邏輯空間按照同樣大小也劃分成若干頁面,稱為邏輯頁面也稱為頁。程式的各

    、分段和儲存管理方式

    1.分頁管理   分頁儲存管理是將一個程序的邏輯地址空間分成若干個大小相等的片,稱為頁面或頁,併為各頁加以編號,從0開始,如第0頁、第1頁等。相應地,也把記憶體空間分成與頁面相同大小的若干個儲存塊,稱為(物理)塊或頁框(frame),也同樣為它們加以編號,如0

    linux核心--管理記憶體的方法

    一、概念 實體地址(physical address) 用於記憶體晶片級的單元定址,與處理器和CPU連線的地址匯流排相對應。 ——這個概念應該是這幾個概念中最好理解的一個,但是值得一提的是,雖然可以直接把實體地址理解成插在機器上那根記憶體本身,把記憶體看成一 個從0位元組一

    易學筆記-系統分析師考試-第3章 作業系統基本原理/3.3 記憶體管理/3.3.3 管理

    分頁式儲存管理 概念:為了避免分割槽式管理產生儲存碎片和管理複雜的問題,分頁式管理把作業的邏輯地址劃分成若干個相等的區域(稱為頁),記憶體空間也劃分成若干個與頁長度相等的區域(也稱為頁幀或塊),然後把頁裝載到頁幀中 特點 頁幀可以是連續的,也可以是不連續的

    3、作業系統記憶體管理——(虛擬記憶體

    注:參考哈工大李治軍老師公開課。本小節需要之前分段與分頁為基礎 對使用者而言,分段是對記憶體的有效使用;而對於計算機而言,分頁可以提高記憶體的使用效率。作業系統需要滿足兩個方面的需求,所以就採取了段頁相結合的方式來管理記憶體。 對於使用者而言,當用戶發出一個邏輯地址,

    作業系統記憶體管理——分割槽、、段式、管理

    1. 記憶體管理方法         記憶體管理主要包括虛地址、地址變換、記憶體分配和回收、記憶體擴充、記憶體共享和保護等功能。  2. 連續分配儲存管理方式 連續分配是指為一個使用者程式分配連續的記憶體空間。連續分配有單一連續儲存管理和分割槽式儲管理兩種方式。 2

    操作系統筆記(十)內存管理之分,分段和

    分段式內存管理 筆記 關系 代碼 保護 系統 長度 段頁式內存管理 bit 基本內存管理: 進程占用空間必須連續,導致外部碎片以及附加的compaction 整個進程的swap in 和 swap out十分耗時。 解決:分頁 ->內存空間不必連續,無外部碎片,

    儲存管理、段式、儲存

    首先看一下“基本的儲存分配方式”種類:        1.  離散分配方式的出現 由於連續分配方式會形成許多記憶體碎片,雖可通過“緊湊”功能將碎片合併,但會付出很大開銷。於是出現離散分配方式

    虛擬記憶體-儲存管理演算法

    在請求分頁儲存管理系統中,由於使用了虛擬儲存管理技術,使得所有的程序頁面不是一次性地全部調入記憶體,而是部分頁面裝入。 這就有可能出現下面的情況:要訪問的頁面不在記憶體,這時系統產生缺

    作業系統儲存管理之分段式與虛擬儲存系統

    分段式虛擬儲存系統 分段式虛擬儲存系統把作業的所有分段的副本都存放在輔助儲存器中,當作業被排程投入執行時,首先把當前需要的一段或幾段裝入主存,在執行過程中訪問到不在主存的段時再把它們裝入。因此,在段表中必須說明哪些段已在主存,存放在什麼位置,段長是多少。哪些段

    Linux核心原始碼分析--記憶體管理(一、分機制)

            Linux系統中分為幾大模組:程序排程、記憶體管理、程序通訊、檔案系統、網路模組;各個模組之間都有一定的聯絡,就像蜘蛛網一樣,所以這也是為什麼Linux核心那麼難理解,因為不知道從哪裡開始著手去學習。很多人會跟著系統上電啟動 BIOS-->bootse

    儲存管理、段式、儲存 以及 優缺點

    記憶體管理方式主要分為:頁式管理、段式管理和段頁式管理。 頁式管理的基本原理是將各程序的虛擬空間劃分為若干個長度相等的頁。把記憶體空間按頁的大小劃分為片或者頁面,然後把頁式虛擬地址與記憶體地址建立一一對應的頁表,並用相應的硬體地址轉換機構來解決離散地址變換問題。頁式管理採用

    記憶體不就夠了?為什麼還要分段?還有

    你好,我是 yes。 關於記憶體訪問你可能聽過分段,分頁,還有段頁式。 但是為什麼要分段?又為什麼要分頁? 有了分頁為什麼還要分段? 這就需要看一看歷史的發展,知曉歷史之後就知道這一切其實都是自然而然的。 這些概念也不是硬塞出來的。 ## 正文 1971 年 11 月 15 日,Intel 推出

    Linux叢集儲存——day1——udev管理技術、多路徑服務

    儲存技術分為:直連式儲存,網路儲存 直連儲存 SCSI介面(雙絞線) 小型計算介面、其介面對應的系統中的磁碟名是  sd開頭 DAS技術 將儲存裝置通過SCSI介面或者光纖通道連結計算機