1. 程式人生 > >深入理解Linux記憶體管理

深入理解Linux記憶體管理

Linux存在於各種體系結構上,所以描述記憶體需要架構獨立的方法。本章會描述用來管理memory bank,頁框的資料結構以及那些影響VM行為的flags

VM第一個重要的流行概念是Non Uniform Memory Access(NUMA)。對於大型機來說,不同banks內的記憶體由於和處理器的距離不同,訪問代價也不同。比如,一個記憶體bank可以指定給每一個CPU,或者一個非常適合DMA操作的記憶體bank可以指定給它附近的裝置或者卡。

我們把bank稱為一個node,在linux系統中不管是Non Unifomr Memory Access(NUMA)還是Uniform Memory Access(UMA),都使用struct pglist_data (typedef pg_data_t)來表示一個節點。系統中的每一個節點都存放在連結串列pgdat_list中,node通過pg_data_t->node_next連結到下一個node。對於PC desktops這樣的UMA結構,僅僅有一個稱為contig_page_data的靜態pg_data_t結構。我們將在2.1節繼續討論node

每個node又被劃分為多個記憶體區,這些記憶體區稱之為zone。struct zone_struct用來描述zone,zone有如下型別:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。不同型別的zone用做不同的場合。ZONE_DMA zone包含低端實體記憶體適合於那些無法訪問超過16MB實體記憶體的裝置。ZONE_NORMAL則被直接對映到kernel線性地址空間的低地址區。我們將在4.1節進一步討論。ZONE_HIGHMEM則是那些無法直接對映到kernel空間的剩餘記憶體。

對於X86機器,記憶體zones如下:

ZONE_DMA            First 16MiB of memory

ZONE_NORMAL    16MiB - 896MiB

ZONE_HIGHMEM   896MiB - End



核心大部分操作都是使用ZONE_NORMAL,所以ZONE_NORMAL是效能最關鍵的zone,在2.2節我們將進一步討論這些zones。 系統記憶體是由固定尺寸的page frames組成,物理page frame由一個struct page表示,這些page structs都儲存在全域性mem_map陣列中,mem_map通常儲存在ZONE_NORMAL的起始位置或者核心映象保留區域的後面。


我們會在2.4節詳細討論struct page,在3.7節詳細討論全域性mem_map陣列。圖2.1演示了這些資料結構之間的關係。

因為可以被核心直接訪問的記憶體數目(ZONE_NORMAL區大小)是有限的,Linux通過high memory支援更多的實體記憶體,high memory我們將在2.7 節討論。在介紹high memory管理之前,先討論nodes,zones和pages是如何表示的。

2.1 Nodes

之前已經提到過,每一個記憶體node由pg_data_t描述,也就是型別strcut pglist_data。當分配一個page時,Linux使用本地節點分配策略從距離當前CPU最近的node分配記憶體,因為程序很有可能也在這個CPU上執行。這個資料結構在<linux/mmzone.h>中定義

struct bootmem_data;
typedef struct pglist_data {
    struct zone node_zones[MAX_NR_ZONES];
    struct zonelist node_zonelists[MAX_ZONELISTS];
    int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
    struct page *node_mem_map;
#ifdef CONFIG_CGROUP_MEM_RES_CTLR
    struct page_cgroup *node_page_cgroup;
#endif
#endif
#ifndef CONFIG_NO_BOOTMEM
    struct bootmem_data *bdata;
#endif
#ifdef CONFIG_MEMORY_HOTPLUG
    /*
     * Must be held any time you expect node_start_pfn, node_present_pages
     * or node_spanned_pages stay constant.  Holding this will also
     * guarantee that any pfn_valid() stays that way.
     *
     * Nests above zone->lock and zone->size_seqlock.
     */
    spinlock_t node_size_lock;
#endif
    unsigned long node_start_pfn;
    unsigned long node_present_pages; /* total number of physical pages */
    unsigned long node_spanned_pages; /* total size of physical page
                         range, including holes */
    int node_id;
    wait_queue_head_t kswapd_wait;
    struct task_struct *kswapd;
    int kswapd_max_order;
} pg_data_t;

資料結構成員簡介

node_zones: 是一個數組,包含這個節點內的zones

node_zonelists: 這是node的分配順序,在mm/page_alloc.c中的build_zonelists()函式建立這個node_zonelists,指定了備用節點列表,當前節點沒有可用空間時,在備用節點分配記憶體。比如在ZONE_HIGHMEM分配失敗,會嘗試從ZONE_NORMAL分配,如果ZONE_NORMAL失敗則嘗試從ZONE_DMA分配。

nr_zones: 這個node包含的zone數目,通常是1 ~ 3。並不是所有的node都有三個zones,比如一個bank可能沒有ZONE_DMA。

node_mem_map:node由多個physical frame組成,每一個物理頁框都有一個page結構表示,node_mem_map是第一個物理頁框的page結構,這個page結構在全域性mem_map陣列的某個位置。

bdata:在系統啟動期間,記憶體管理子系統那個初始化之前,核心也需要使用記憶體。boot memory allocator使用這個成員,我們將在第五章討論boot memory allocator

node_start_pfn:是該節點內第一個頁幀的邏輯編號。系統中所有節點的頁幀是依次編號的,每個頁幀號也是全域性唯一的。

node_present_pages:指定了節點中頁幀的數目,

node_spanned_pages:該節點包含的頁幀範圍,包括holes。

系統中所有的nodes都通過list pgdat_list維護。在初始化函式init_bootmem_core()中建立這個list,我們將在5.3節討論pgdat_list的初始化。在kernel 2.4.18之前,遍歷pgdat_list連結串列的程式碼如下:

pg_data_t *pgdat;
pgdat = pgdat_list;
do {
    /* do something with pgdata_t */

} while ((pgdat = pgdat->node_next));

在最近的kernel版本,巨集for_each_pgdat提供了遍歷pgdat_list的功能。

2.2 Zones

每個zone通過struct zone_struct描述,struct zone_struct儲存著頁面使用的統計資訊,空閒資訊和locks,這個結構在<linux/mmzone.h>中宣告:

typedef struct zone_struct {
    spinlock_t       lock;
    unsigned long    free_pages;
    unsigned long    pages_min, pages_low, pages_high;
    int              need_balance;

    free_area_t      free_area[MAX_ORDER];
    wait_queue_head_t *wait_table;
    unsigned long    wait_table_size;
    unsigned long    wait_table_shift;

    struct pglist_data *zone_pgdat;
    struct page      *zone_mem_map;
    unsigned long    zone_start_paddr;
    unsigned long    zone_start_mapnr;

    char             *name;
    unsigned long    size;
} zone_t;

資料結構成員簡介:

lock:一個spinlock,保護對zone的併發訪問

free_pages:zone內空閒pages的總數

pages_min, page_low, pages_high:這個zone的watermark,這三個值會影響交換守護程序的行為,2.3節詳細描述這幾個watermark。我們從這裡可以看出,交換守護程序是針對每個zone的使用情況進行處理的。

need_balance:這個flag用來通知kswapd balance這個zone。當一個zone的可用頁面數目達到zone watermark時,標記need_balance標誌。

free_area:zone 空閒區點陣圖,標識每一個page的使用情況,buddy分配器使用這個點陣圖來進行分配和釋放。

wait_table:page頁面上程序等待佇列的hash table,wait_on_page()和unlock_page函式將使用這個hsah table。我們將在2.2.3節討論wait_table

wait_table_size:這個hash table的等待佇列數目,是2的冪次方

wait_table_shift:

zone_pgdat:指向zone所在node 對應的結構pg_data_t

zone_mem_map:這個zone中第一個物理頁框對應page

zone_start_paddr:這個zone的起始實體地址

zone_start_mapnr:第一個物理頁框對應的頁幀號,也就是在全域性mem_map中的偏移

name:描述這個zone的字串:“DMA”, “Normal”和“HighMem”

size:zone所包含的頁框數目

2.2.1 Zone Watermarks

當系統的可用記憶體變低時,頁面pageout守護程序kswapd被喚醒,開始釋放空閒pages。在壓力較高的情況下,守護程序立刻同步的釋放記憶體,我們稱之為direct-reclaim。這些控制pageout行為的引數在FreeBSD和Solaris都有類似實現。

每一個zone都有三個watermarks:分別稱為pages_low,pages_min和pages_high,可以用來判斷zone當前的記憶體壓力。pages_min在free_area_init_core()中計算,計算公式為ZoneSizeInPages/128,但是最低不低於20 pages,最高不超過255 pages。

系統根據zone內page使用情況,在處於不同的watermark時,會採取不同的動作

pages_low: 當空閒頁面數目低於pages_low時,kswapd被buddy allocator喚醒釋放pages。值預設是pages_min的兩倍。

pages_min: 當空閒頁面數目低於pages_min時,allocator將代替kswapd同步釋放pages,也就是直接回收

pages_high: 當kswapd被喚醒來釋放pages時,回收的pages已經達到pages_high標記的頁面數,kswapd將停止回收,進入休眠。預設值一般為pages_min的三倍。

不管這些引數在類似系統中叫什麼名字,作用都是類似的,輔助pageout守護程序或者執行緒進行頁面回收。

2.2.2 Calculating the Size of Zones

zone的尺寸是在函式setup_memory中計算,如圖2.3


PFN物理頁框號,是物理頁框在記憶體中的page 偏移。系統內的第一個PFN,儲存在min_low_pfn,是loaded kernel映象後面第一個page。這個值儲存在mm/bootmem.c中

最後一個物理頁框max_pfn是怎麼計算的呢? 不同的系統計算方法不同

max_low_pfn是指低端最大物理頁框號,用來標記ZONE_NORMAL的結尾。這是核心可以直接訪問的最大實體記憶體,這個地址值和kernel/userspace地址空間劃分相關。這個值儲存在mm/bootmem.c中,對於低記憶體系統,max_pfn是等於max_low_pfn的。

使用min_low_pfn, max_low_pfn和max_pfn,可以很容易的計算出high memory的起始和結束地址 highstart_pfn, highend_pfn。

2.2.3 Zone Wait Queue Table

2.3 Zone Initialization

2.4 Initializing mem_map

mem_map 在系統啟動時建立,在UMA系統中free_area_init()使用contig_page_data作為node,global mem_map作為這個ode的本地mem_map。

free_area_init_core()為當前節點分配一個本地mem_map。mem_map陣列是通過boot memory 分配器alloc_bootmem_node來分配的。

2.5 Pages

系統內的每一個物理page frame都有一個相應的page來跟蹤這個page的狀態,在2.2kernel,這個結構和System V中的類似,但是對於其他的UNIX變種,這個結構做了寫改變。struct page在<linux/mm.h>中定義:

typedef struct page {
    struct list_head list;
    struct address_space *mapping;
    unsigned long index;
    struct page *next_hash;
    atomic_t count;
    unsigned long flags;
    struct list_head lru;
    struct page **prev_hash;
    struct buffer_head *buffers;

#if defined(CONFIG_HIGHMEM) || defined(WAIT_PAGE_VIRTUAL)
    void *virtual;
#endif
} mem_map_t;

下面是page結構中成員變數的簡介:

list:pages可以屬於多種連結串列,這個list成員就是用來掛接到這些連結串列的。例如,一個對映的pages可以屬於address_space中三個迴圈連結串列中的一個。他們是clean_pages, dirty_pages和locked_pages。在slab分配器中,當一個page已經被slab分配器分配,它被用來儲存page所管理的slab和cache結構指標。它也可以用來把page內空閒block連線到一起

mapping:當檔案和裝置做記憶體mapped時,他們的inode有一個address_space。page的這個成員就指向這個address space如果pages屬於這個檔案。如果page 是匿名對映,那麼address_space指向swapper_space。

index:這個成員有兩個用途,具體是哪一個取決於page的狀態。如果page是檔案對映的一部分,那麼index是檔案內的頁偏移。如果page是swap cache的一部分,那麼index則是交換地址空間swapper_space內的偏移量。如果pages內的塊正在被特定程序釋放中,正在被釋放block的order存放在index中,這個值被函式_free_pages_ok()設定。

next_hash:如果Pages作為檔案對映的一部分,那麼pages使用inode和offset做hash。這個next_hash把具有相同hash的pages連結到一起。

count:page的索引計數。如果變為0,那麼這個page可以被釋放。如果大於0,那麼說明他被一個或者多個程序使用,或者被kernel使用中

flags:儲存了體系結構無關的標誌,用來描述page的狀態。這些狀態標記在<linux/mm.h>中定義,表2.1中也列除了這些狀態標記。

lru:對於page替換策略,active_list或者inactive_list連結串列中的pages可能會被交換出。lru是這些這些可交換pages的Least Recently Used 連結串列的連結串列頭,我們將在chapter 10討論這兩個連結串列。

pprev_hash:和next_hash互為補充,形成一個雙向連結串列。

buffers:如果一個page和一個塊裝置的buffer相關聯,那麼這個成員用來記錄buffer_head。如果一個匿名page被交換檔案後備時,那麼這個page也有相應的buffer_head。因為這個page不得不同步資料到後備storage的block-size 塊中。

virtual:正常情況下,僅僅ZONE_DMA和ZONE_NORMAL的pages被kernel直接對映。對於ZONE_HIGHMEM的pages,無法直接對映到核心記憶體中的頁,當一個page被對映後,這個成員記錄了page的虛擬地址。

2.6 Mapping Pages to Zones

最近的kernel版本2.4.18,struct page增加了成員指標zone,指向page所在的zone。後來這個成員被認為沒什麼作用,因為即便是一個指標,對於大量的struct page,仍然消耗許多的記憶體。在最近的kernel,這個zone成員被去掉了。取而代之的是通過page->flags中的最高ZONE_SHIFT位來決定page屬於哪個zone。這些位記錄了page對應的zone在zone_table中的索引。

在系統啟動時會建立zones的zone_table,zone_table在mm/page_alloc.c中宣告如下:

zone_t *zone_table[MAX_NR_ZONES*MAX_NR_NODES];
EXPORT_SYMBOL(zone_table);

MAX_NR_ZONES是一個node中zones的最大數目,比如3。MAX_NR_NODES是系統nodes的最大數目。函式EXPORT_SYMBOL使得zone_table可以被loadable modules訪問。這個表就像一個多維陣列。在free_area_init_core時,node內的所有pages都被初始化。首先把這個zone記錄到zone_table中

zone_table[nid * MAX_NR_ZONES + j] = zone

nid是node的ID,j是zone在這個node中的索引,zone是zone_t結構,對於每一個page,都會執行函式set_page_zone

set_page_zone(page, nid * MAX_NR_ZONES + j)

引數@page的zone index被設定為nid * MAX_NR_ZONES+j

2.7 High Memory

因為核心可用的地址空間(ZONE_NORMAL)大小是有限的,所以kernel通過high  memory支援更多的實體記憶體。在32-bit x86系統上有兩個閥值,一個是4GiB,一個是64GiB。

4GiB限制是32-bit實體地址最大可定址空間,為了訪問1GiB到4GiB範圍的記憶體(1GiB這個下限並不是確定的,不僅和預留的vmalloc空間有關,而且還和kernel/user space的地址空間劃分有關),kernel需要使用kmap臨時對映pages到ZONE_NORMAL。我們將在第九章進一步討論。

第二個限制64GiB是和PAE相關的,Intel的發明允許在32-bit系統上訪問更多的RAM。它使用額外的位進行記憶體定址,定址範圍可以達到2^36(64GiB)

雖然理論上PAE允許定址範圍是64GiB的,但是實際中linux程序無法訪問那麼多的RAM,因為虛擬地址空間仍然是4GiB。如果使用者想在他們的程序內使用malloc分配所有的實體記憶體,那麼是不可能的。

此外PAE也不允許kernel本身有那麼多的RAM,結構page被用來描述每一個頁框,結構page的大小為44位元組,存放在ZONE_NORMAL這個虛擬地址空間中。這意味著1GiB的實體記憶體將佔用11MiB的記憶體,而對於16GiB則是176MiB,這就導致ZONE_NORMAL變得更小。雖然目前看起來還不算太壞,但是我們再考慮其他小結構體,比如Page Table Entry(PTEs),那麼在最壞的情況下需要16MiB(16GiB記憶體需要4M個PTEs)空間。這使得16MiB成為x86 linux可用實體記憶體的實際限制。如果需要訪問更多的實體記憶體,給個簡單明瞭的建議,使用64-bit系統。


相關推薦

深入理解Linux記憶體管理

Linux存在於各種體系結構上,所以描述記憶體需要架構獨立的方法。本章會描述用來管理memory bank,頁框的資料結構以及那些影響VM行為的flags VM第一個重要的流行概念是Non Uniform Memory Access(NUMA)。對於大型機來說,不同bank

深入理解PHP記憶體管理之誰動了我的記憶體

首先讓我們看一個問題: 如下程式碼的輸出, var_dump(memory_get_usage()); $a = "laruence"; var_dump(memory_get_usage()); unset($a); var_dump(memory_get_usage()); 輸出

深入理解 Linux 核心---記憶體管理

核心中的函式以比較直接了當的方式獲得動態記憶體: __get_free_pages() 或 alloc_pages() 從分割槽頁框分配器獲得頁框。 kmem_cache_alloc() 或 kmalloc() 使用 slab 分配器為專用或通用物件分配塊。 vm

深入理解Linux核心】記憶體定址(一)

  1. 邏輯地址:包含在機器語言指令中用來指定一個運算元或一條指令的地址。每一個邏輯地址都由一個段和偏移量組成。偏移量指明瞭從段開始的地方到實際地址之間的距離。 2. 線性地址:又稱虛擬地址,是一個32位無符號整數,也用來表示4GB的地址,範圍從0x00000000到0xffff

深入理解Linux核心day01--記憶體定址

記憶體定址 記憶體地址:     邏輯地址: 段+偏移量 組成     線性地址: 可用來表達4GB的地址 (也稱虛擬地址)     實體地址: 用於記憶體晶片級記憶體單元定址。他們與微處理器地址引腳傳送到記憶體總線上的電訊號相對應     記憶體控制單元(MMU) 通過一

深入理解Linux內核》軟中斷/tasklet/工作隊列

可重入函數 根據 函數指針等 上半部 應該 可重入 運行 最好的 內核編譯 軟中斷、tasklet和工作隊列並不是Linux內核中一直存在的機制,而是由更早版本的內核中的“下半部”(bottom half)演變而來。下半部的機制實際上包括五種,但2.6版本的內核中,下半部和

深入理解Linux內核 - 第二章 內存尋址 01

lin linu 地址 什麽 邏輯地址 問題 理解 整數 深入 1,三個地址 邏輯地址,機器語言指令中用來指定一個操作數或一條指令的地址。 線性地址:32位無符號整數,高達4GB。64位的cpu就是64位的線性地址 物理地址:內存芯片級內存單元尋址。老式x86由32位或36

Linux記憶體管理(最透徹的一篇)

摘要:本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理與使用者記憶體管理之

【轉】Linux記憶體管理

摘要:本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理與使用者記憶體管理之間的

linux記憶體管理的 夥伴系統和slab機制

夥伴系統 Linux核心中採用了一種同時適用於32位和64位系統的記憶體分頁模型,對於32位系統來說,兩級頁表足夠用了,而在x86_64系統中,用到了四級頁表。四級頁表分別為:  頁全域性目錄(Page Global Directory) 頁上級目錄(Page Upper Director

linux記憶體管理---虛擬地址 邏輯地址 線性地址 實體地址的區別(一)

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

深入理解 Java 記憶體模型》讀書筆記(下)(乾貨,萬字長文)

0. 前提 1. 基礎 2. 重排序 3. 順序一致性 4. Volatile 5. 鎖 6. final 7. 總結 4. Volatile 4.1 VOLATILE 特性 舉個例子: publ

深入理解 Java 記憶體模型》讀書筆記(上)(乾貨,萬字長文)

0. 前提 1. 基礎 2. 重排序 3. 順序一致性 4. Volatile 5. 鎖 6. final 7. 總結 0. 前提 《深入理解 Java 記憶體模型》 程曉明著,該書在以前看過一

深入理解linux核心kfifo【轉】

  (轉自:http://blog.chinaunix.net/uid-18770639-id-4203078.html) 專案中要用到ringbuffer,一直都是自己造輪子,除錯中才發現經常會出問題,主要是沒有加記憶體屏障。近期自己學習了linux kernel的kfifo,才

Linux記憶體管理之malloc實現

程序虛擬地址空間由一個一個VMA來表示,這裡先接收VMA相關操作. 1.1 查詢VMA函式find_vma() struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr) 找到的vma結果需滿足條件:

Linux記憶體描述之記憶體節點node--Linux記憶體管理(二)

1 記憶體節點node 1.1 為什麼要用node來描述記憶體 這點前面是說的很明白了, NUMA結構下, 每個處理器CPU與一個本地記憶體直接相連, 而不同處理器之前則通過匯流排進行進一步的連線, 因此相對於任何一個CPU訪問本地記憶體的速度比訪問遠端記憶體的速度要快 Linux適用於各種不同的體系結

Linux記憶體描述之記憶體區域zone--Linux記憶體管理(三)

1 記憶體管理域zone 為了支援NUMA模型,也即CPU對不同記憶體單元的訪問時間可能不同,此時系統的實體記憶體被劃分為幾個節點(node), 一個node對應一個記憶體簇bank,即每個記憶體簇被認為是一個節點 首先, 記憶體被劃分為結點. 每個節點關聯到系統中的一個處理器, 核心中表示為pg_

Linux記憶體描述之記憶體頁面page--Linux記憶體管理(四)

1 Linux如何描述實體記憶體 Linux把實體記憶體劃分為三個層次來管理 層次 描述 儲存節點(Node) CPU被劃分為多個節點(node), 記憶體則被分簇, 每個CPU對應一個本地實體記憶體, 即一個CPU-node對應

Linux分頁機制之概述--Linux記憶體管理(六)

1 分頁機制 在虛擬記憶體中,頁表是個對映表的概念, 即從程序能理解的線性地址(linear address)對映到儲存器上的實體地址(phisical address). 很顯然,這個頁表是需要常駐記憶體的東西, 以應對頻繁的查詢對映需要(實際上,現代支援VM的處理器都有一個叫TLB的硬體級頁表快取部件

Linux記憶體管理 (透徹分析)

摘要: 本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理