Linux頁框管理

在前面的博文裏,我們講解了基於80x86體系的Linux內核分段和分頁機制,並詳細地討論了Linux的內存佈局。有了這些基本概念以後,我們就來詳細討論內核如何動態地管理那些可用的內存空間。

對於80386這種32位的處理器結構,Linux採用4KB頁框大小作爲標準的內存分配單元。內核必須記錄每個頁框的當前狀態,例如,區分哪些頁 框包含的是屬於進程的頁,而哪些頁框包含的是內核代碼或內核數據。內核還必須能夠確定動態內存中的頁框是否空閒,如果動態內存中的頁框不包含有用的數據, 那麼這個頁框就是空閒的。在以下情況下頁框是不空閒的:包含用戶態進程的數據、某個軟件高速緩存的數據、動態分配的內核數據結構、設備驅動程序緩衝的數 據、內核模塊的代碼等等。

內核用數據結構page描述一個頁框的狀態信息,所有的頁描述符存放在全局mem_map數組中,其數組的下標爲頁框號(pfn)。 因爲每個描述符長度爲32字節,所以mem_map所需要的空間略小於整個RAM的1%。

那麼一個頁描述符怎樣與一個佔據4k的頁框相聯繫(映射)呢?有了mem_map數組,這個問題就很簡單了。因爲如果知道了page數據的地址pd,用pd去減去mem_map就得到了pd的頁框號pfn。那麼這個物理頁的物理地址是physAddr = pfn << PAGE_SHIFT 


在得知該物理頁的物理地址是physAddr後,就可以視physAddr的大小得到它的虛擬地址: 
1.physAddr < 896M  對應虛擬地址是 physAddr + PAGE_OFFSET    (PAGE_OFFSET=3G) 
2.physAddr >= 896M 對應虛擬地址不是靜態映射的,通過內核的高端虛擬地址映射得到一個虛擬地址。

在得到該頁的虛擬地址之後,內核就可以正常訪問這個物理頁了。

內核提供一個virt_to_page(addr)宏來產生線性地址addr對應的頁描述符地址。pfn_to_page(pfn)宏產生與頁框號 pfn對應的頁描述符地址。相反,也提供page_to_pfn(pg)宏來產生頁描述符對應的頁的頁框號pfn。注意,針對80x86結構,上述宏並不 是直接通過men_map數組來確定頁框號的,而是通過內存管理區的zone_mem_map來確定的,不過原理是一樣的:

#define page_to_pfn(pg)       /
({         /
 struct page *__page = pg;     /
 struct zone *__zone = page_zone(__page);   /
 (unsigned long)(__page - __zone->zone_mem_map)   /
  + __zone->zone_start_pfn;    /
})

這裏千萬要注意!不要混淆一個概念。這裏的physAddr雖然表示物理地址,但是並不能說明該地址的數據就一定存在於物理內存中。那麼如何判斷這個頁到底在不在內存中呢?你看,前面的知識就用到了——分頁機制。 也就是說,如果這個頁因爲各種各樣五花八門的原因被交換出去了,那麼它對應的頁的Present標誌就爲0。這裏就牽涉到缺頁異常了,要深入瞭解,請關注筆者後面的博文。

在這裏我們只需要對數據結構page詳細討論以下兩個字段:
1、_count:頁的引用計數器。如果該字段爲-1,則相應頁框空閒,並可被 分配給任一進程或內核本身;如果該字段的值大於或等於0,則說明頁框被分配給一個或多個進程,或用於存放一些內核數據結構。page_count()函數 返回_count加1後的值,也就是該頁的使用者的數目。
2、flags:包含多達32個用來描述頁框狀態的標誌。對於每個PG_xyz標誌,內核都定義了操縱其值的一些宏。通常,PageXyz宏返回標誌的值,而SetPageXyz和ClearPageXyz宏分別設置和清除相應的位。

 

 

標誌名

含義

PG_locked

頁被鎖定,例如,在磁盤I/O 操作中涉及的頁。

PG_error

在傳輸頁時發生錯誤

PG_referenced

剛剛訪問過的頁

PG_uptodate

在完成讀操作後置位,除非發生磁盤I/O 錯誤

PG_dirty

頁已經被修改

PG_lru

頁在活動或非活動頁鏈表中

PG_active

頁在活動頁鏈表中

PG_slab

包含在slab 中的頁框

PG_highmem

頁框屬於ZONE_HIGHMEM 管理區

PG_checked

由一些文件系統(如Ext2 和Ext3 )使用的標誌

PG_arch_1

在80x86 體系結構上沒有使用

PG_reserved

頁框留給內核代碼或沒有使用

PG_private

頁描述符的private 字段存放了有意義的數據

PG_writeback

正在使用writepage 方法將頁寫到磁盤上

PG_nosave

系統掛起 / 喚醒時使用

PG_compound

通過擴展分頁機制處理頁框

PG_swapcache

頁屬於對換高速緩存

PG_mappedtodisk

頁框中的所有數據對應於磁盤上分配的塊

PG_reclaim

爲回收內存對頁已經做了寫入磁盤的標記

PG_nosave_free

系統掛起 / 恢復時使用

 

系統是怎麼爲進程或內核分配一個內存空間,或者說怎麼給他們分配一個線性頁描述符所對應線性地址的頁面呢?這個需要藉助內核的分區頁框分配器和夥伴系統算法。在討論這些細節之前,先介紹一些必要的概念。

1 非統一內存訪問(NUMA)架構

Linux2.6支持非統一內存訪問(NUMA)模型,在這種模型中,給定CPU對不同內存單元的訪問時間可能不一樣。系統的物理內存被劃分爲幾個 節點(node)。在一個單獨的節點內,任一給定CPU訪問頁面所需要的時間都是相同的,而對於不同的CPU,這個時間就不同。對每個CPU而言,內核都 試圖把耗時節點的訪問次數減到最少,這就必須要將那些CPU最常引用的內核數據結構的存放位置選好。

每個節點由一個類型爲pg_data_t的描述符表示,所有節點的描述符存放在一個單向鏈表中,它的第一個元素由內核全局變量pgdat_list 指向。在x86體系中,即使是多核,內存訪問時間也是相同的,所以不需要NUMA,但是內核還是使用節點,不過,這只是一個單獨的節點,它包含了系統中所 有的物理內存。因此,pgdat_list變量指向一個鏈表,此鏈表只有一個元素組成的,這個元素就是節點0描述符,它被存放在 contig_page_data變量中。

pg_data_t描述符中要注意到的三個字段分別是node_zones、node_zonelists、node_mem_map,分別是 zone_t[]、zonelist_t[]和page類型。前兩個是用來描述內存管理區的,下面馬上要談到;node_mem_map是本節點所有頁的 頁描述符數組。內核將這三個字段放在裏邊,就是爲內存區、頁框建立一些列的聯繫。

2 內存管理區

由於Linux內核必須處理80x86體系結構中的兩種硬件約束:
(1)ISA總線的直接內存存取(DMA)處理器有一個嚴格的限制:他們只能對RAM的前16MB尋址。
(2)在具有較大容量RAM的現代32位計算機中,CPU不能直接訪問所有的物理內存,因爲線性地址空間太小。

爲了應對這兩種限制,Linux2.6把每個內存節點的物理內存劃分成3個管理區(zone)。在80x86的UMA體系結構中的管理區分爲:
ZONE_DMA:包含低於16MB的內存頁框
ZONE_NORMAL:包含高於16MB而低於896MB的內存頁框
ZONE_HIGHMEM:包含從896MB開始高於896MB的內存頁框

ZONE_DMA和ZONE_NORMAL區包含內存“常規”頁框,通過把他們線性地址映射到線性地址空間的第4個GB,內核就可以直接進行訪問。 ZONE_HIGHMEM區包含的內存頁不能由內核直接訪問,儘管它們也可以通過高端內存內核映射,線性映射到線性地址空間的第4個GB。

每個內存管理區都有自己的描述符zone_t,其字段中很多用於回收頁框時使用。其實每個頁描述符page都有到內存節點和到內存節點管理區的鏈 接。那我們爲啥看不到呢,原因是爲了節省空間,這些鏈接的存放方式和典型的指針不同,是被編碼成索引存放在flags字段的高位。

 zone_t字段如下:

 

類型

名稱

說明

unsigned long

free_pages

管理區中空閒頁的數量

unsigned long

pages_min

管理區中保留頁的數目

unsigned long

pages_low

回收頁框使用的下界;同時也被管理區分配器作爲閾值使用

unsigned long

pages_high

回收頁框使用的上界;同時也被管理區分配器作爲閾值使用

unsigned long []

lowmem_reserve

指明在處理內存不足的臨界情況下每個管理區必須保留的頁框數目

struct per_cpu_pageset[]

pageset

數據結構用於實現單一頁框的特殊高速緩存

spinlock_t

lock

保護該描述符的自旋鎖

struct free_area []

free_area

標識出管理區中的空閒頁框塊

spinlock_t

lru_lock

活動以及非活動鏈表使用的自旋鎖

struct list head

active_list

管理區中的活動頁鏈表

struct list head

inactive_list

管理區中的非活動頁鏈表

unsigned long

nr_scan_active

回收內存時需要掃描的活動頁數目

unsigned long

nr_scan_inactive

回收內存時需要掃描的非活動頁數目

unsigned long

nr_active

管理區的活動鏈表上的頁數目

unsigned long

nr_inactive

管理區的非活動鏈表上的頁數目

unsigned long

pages_scanned

管理區內回收頁框時使用的計數器

int

all_unreclaimable

在管理區中填滿不可回收頁時此標誌被置位

int

temp_priority

臨時管理區的優先級(回收頁框時使用)

int

prev_priority

管理區優先級,範圍在 12 和 0 之間(由回收頁框算法使用)

wait_queue_head_t *

wait_table

進程等待隊列的散列表,這些進程正在等待管理區中的某頁

unsigned long

wait_table_size

等待隊列散列表的大小

unsigned long

wait_table_bits

等待隊列散列表數組大小,值爲2order

struct pglist_data *

zone_pgdat

內存節點

struct page *

zone_mem_map

指向管理區的第一個頁描述符的指針

unsigned long

zone_start_pfn

管理區第一個頁框的下標

unsigned long

spanned_pages

以頁爲單位的管理區的總大小,包括洞

unsigned long

present_pages

以頁爲單位的管理區的總大小,不包括洞

char *

name

指針指向管理區的傳統名稱:“DMA ”,“NORMAL ”或“HighMem ”

實際上,刻畫頁框的標誌的數目是有限的,因此保留flags字段的最高位來編碼內存節點和管理區是綽綽有餘的。Linux提供 page_zone()函數用來接收一個頁描述符的地址作爲它的參數;它讀取該描述符中的flags字段的最高位,然後通過查看zone_table數組 來確定相應管理區描述符的地址。順便提一下,在系統啓動時用,內核將所有內存節點的所有管理區描述符的地址放到這個zone_table數組裏邊。

當內核調用一個內存分配函數時,必須指明請求頁框所在的管理區。內核通常指明它願意使用哪個管理區。爲了在內存分配請求中指定首選管理區,內核使用 zonelist數據結構,這就是管理區描述符指針數組,在80x86中只有三個zone,所以zonelist數據結構中指向這三個zone的指針按照 一定規則排列。如圖,則zonelist數組就是這三個zone的排列組合。

管理區

例如,要分配一個用來做DMA的頁框,則在指定zonelist數組中的某個zonelist元素中獲得首選的zone,應該是ZONE_DMA,如果該區空間已使用完,就選ZONE_NORMA區,隨後再是ZONE_HIGHMEM。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章