linux內存管理(下)

物理內存管理(頁管理)

Linux內核管理物理內存是通過分頁機制實現的,它將整個內存劃分成無數4k(i386體系結構中)大小頁,從而分配和回收內存的基本單位便是內存頁了。利用分頁管理有助於靈活分配內存地址,因爲分配時不必要求必須有大塊的連續內存[1],系統可以東一頁、西一頁的湊出所需要的內存供進程使用。雖然如此,但是實際上系統使用內存還是傾向於分配連續的內存塊,因爲分配連續內存時,頁表不需要更改,因此能降低TLB的刷新率(頻繁刷新會很大增加訪問速度)。

鑑於上述需求,內核分配物理頁爲了儘量減少不連續情況,採用了“夥伴”關係來管理空閒頁框。夥伴關係分配算法大家不應陌生——幾乎所有操作系統書都會提到,我們不去詳細說它了,如果不明白可以參看有關資料。這裏只需要大家明白Linux中空閒頁面的組織和管理利用了夥伴關係,因此空閒頁面分配時也需要遵循夥伴關係,最小單位只能是2的冪倍頁面大小。內核中分配空閒頁框的基本函數是get_free_page/get_free_pages,它們或是分配單頁或是分配指定的頁框(248…512頁)。

 注意:get_free_page是在內核中分配內存,不同於malloc在用戶空間中分配,malloc利用堆動態分配,實際上是調用brk()系統調用,該調用的作用是擴大或縮小進程堆空間(它會修改進程的brk域)。如果現有的內存區域不夠容納堆空間,則會以頁面大小的倍數位單位,擴張或收縮對應的內存區域,但brk值並非以頁面大小爲倍數修改,而是按實際請求修改。因此Malloc在用戶空間分配內存可以以字節爲單位分配,但內核在內部仍然會是以頁爲單位分配的。

   另外需要提及的是,物理頁在系統中由頁框結構struct paga描述,系統中所有的頁框存儲在數組mem_map[]中,可以通過該數組找到系統中的每一頁(空閒或非空閒)。而其中的空閒頁框則可由上述提到的以夥伴關係組織的空閒頁鏈表(free_area[MAX_ORDER]索引。

 內核內存使用

Slab

    所謂尺有所長,寸有所短。以頁爲最小單位分配內存對於內核管理系統物理內存來說的確比較方便,但內核自身最常使用的內存卻往往是很小(遠遠小於一頁)的內存塊——比如存放文件描述符、進程描述符、虛擬內存區域描述符等行爲所需的內存都不足一頁。這些用來存放描述符的內存相比頁面而言,就好比是麪包屑與麪包。一個整頁中可以聚集多個這種這些小塊內存;而且這些小塊內存塊也和麪包屑一樣頻繁地生成/銷燬。

  爲了滿足內核對這種小內存塊的需要,Linux系統採用了一種被稱爲slab分配器的技術。Slab分配器的實現相當複雜,但原理不難,其核心思想就是“存儲池[2]的運用。內存片段(小塊內存)被看作對象,當被使用完後,並不直接釋放而是被緩存到“存儲池”裏,留做下次使用,這無疑避免了頻繁創建與銷燬對象所帶來的額外負載。

Slab技術不但避免了內存內部分片(下文將解釋)帶來的不便(引入Slab分配器的主要目的是爲了減少對夥伴系統分配算法的調用次數——頻繁分配和回收必然會導致內存碎片——難以找到大塊連續的可用內存,而且可以很好利用硬件緩存提高訪問速度。

    Slab並非是脫離夥伴關係而獨立存在的一種內存分配方式,slab仍然是建立在頁面基礎之上,換句話說,Slab將頁面(來自於夥伴關係管理的空閒頁框鏈)撕碎成衆多小內存塊以供分配,slab中的對象分配和銷燬使用kmem_cache_alloc與kmem_cache_free

 

Kmalloc

Slab分配器不僅僅只用來存放內核專用的結構體,它還被用來處理內核對小塊內存的請求。當然鑑於Slab分配器的特點,一般來說內核程序中對小於一頁的小塊內存的求情才通過Slab分配器提供的接口Kmalloc來完成(雖然它可分配32 131072字節的內存)。從內核內存分配角度講kmalloc可被看成是get_free_pages)的一個有效補充,內存分配粒度更靈活了。

有興趣的話可以到/proc/slabinfo中找到內核執行現場使用的各種slab信息統計,其中你會看到系統中所有slab的使用信息。從信息中可以看到系統中除了專用結構體使用的slab外,還存在大量爲Kmalloc而準備的Slab(其中有些爲dma準備的)。

 

 

內核非連續內存分配(Vmalloc)

 

夥伴關係也好、slab技術也好,從內存管理理論角度而言目的基本是一致的,它們都是爲了防止“分片”,不過分片又分爲外部分片和內部分片之說,所謂內部分片是說系統爲了滿足一小段內存區(連續)的需要,不得不分配了一大區域連續內存給它,從而造成了空間浪費;外部分片是指系統雖有足夠的內存,但卻是分散的碎片,無法滿足對大塊“連續內存”的需求。無論何種分片都是系統有效利用內存的障礙。slab分配器使得含與一個頁面內衆多小塊內存可獨立被分配使用,避免了內部分片,節約了空閒內存。夥伴關係把內存塊按大小分組管理,一定程度上減輕了外部分片的危害,因爲頁框分配不在盲目,而是按照大小依次有序進行,不過夥伴關係只是減輕了外部分片,但並未徹底消除。你自己筆畫一下多次分配頁框後,空閒內存的剩餘情況吧。

所以避免外部分片的最終思路還是落到了如何利用不連續的內存塊組合成“看起來很大的內存塊”——這裏的情況很類似於用戶空間分配虛擬內存,內存邏輯上連續,其實影射到並不一定連續的物理內存上。Linux內核借用了這個技術,允許內核程序在內核地址空間中分配虛擬地址,同樣也利用頁表(內核頁表)將虛擬地址影射到分散的內存頁上。以此完美地解決了內核內存使用中的外部分片問題。內核提供vmalloc函數分配內核虛擬內存,該函數不同於kmalloc,它可以分配較Kmalloc大得多的內存空間(可遠大於128K,但必須是頁大小的倍數),但相比Kmalloc來說Vmalloc需要對內核虛擬地址進行重影射,必須更新內核頁表,因此分配效率上要低一些(用空間換時間)

與用戶進程相似內核也有一個名爲init_mmmm_strcut結構來描述內核地址空間,其中頁表項pdg=swapper_pg_dir包含了系統內核空間(3G-4G)的映射關係。因此vmalloc分配內核虛擬地址必須更新內核頁表,而kmallocget_free_page由於分配的連續內存,所以不需要更新內核頁表。

 

空閒頁框

APP

內存區域 vm_area_structs

mallocforkexcutemmap

brk/do_map

get_free_page(s)

用戶空間

內核空間

進程虛擬地址空間

 

系統調用

進程頁表

 

請頁異常

內核程序

物理內存影射區

Vmalloc分配區

slab

get_free_page(s)

內核頁表

get_free_page(s)

請頁異常

vmalloc分配的內核虛擬內存與kmalloc/get_free_page分配的內核虛擬內存位於不同的區間,不會重疊。因爲內核虛擬空間被分區管理,各司其職。進程空間地址分佈從0到3G(其實是到PAGE_OFFSET,0x86中它等於0xC0000000),從3Gvmalloc_start這段地址是物理內存映射區域(該區域中包含了內核鏡像、物理頁框表mem_map等等)比如我使用的系統內存是64M(可以用free看到),那麼(3G——3G+64M)這片內存就應該映射物理內存,而vmalloc_start位置應在3G+64M附近(說附近因爲是在物理內存映射區與vmalloc_start期間還回存在一個8M大小的gap來防止躍界),vmalloc_end的位置接近4G(說接近是因爲最後位置系統會保留一片128k大小的區域用於專用頁面映射,還由可能會由高端內存映射區,這些都是細節,這裏我們不做糾纏)

 

 

 

進程地址空間

物理內存映射區

 

3G

內核虛擬空間

Vmalloc_start

Vmalloc_end

上圖是內存分佈的模糊輪廓

 

  get_free_pageKmalloc函數所分配的連續內存都陷於物理映射區域,所以它們返回的內核虛擬地址和實際物理地址僅僅是相差一個偏移量(PAGE_OFFSET),你可以很方便的將其轉化爲物理內存地址,同時內核也提供了virt_to_phys()函數將內核虛擬空間中的物理影射區地址轉化爲物理地址。要知道,物理內存映射區中的地址與內核頁表是有序對應,系統中的每個物理頁框都可以找到它對應的內核虛擬地址(在物理內存映射區中的)。

vmalloc分配的地址則限於vmalloc_startvmalloc_end之間。每一塊vmalloc分配的內核虛擬內存都對應一個vm_struct結構體(可別和vm_area_struct搞混,那可是進程虛擬內存區域的結構),不同的內核虛擬地址被4k打大小空閒區的間隔,以防止越界——見下圖)。與進程虛擬地址的特性一樣,這些虛擬地址可與物理內存沒有簡單的位移關係,必須通過內核頁表纔可轉換爲物理地址或物理頁。它們有可能尚未被映射,在發生缺頁時才真正分配物理頁框。

 

這裏給出一個小程序幫助大家認請上面幾種分配函數所對應的區域。

#include<linux/module.h>

#include<linux/slab.h>

#include<linux/vmalloc.h>

unsigned char *pagemem;

unsigned char *kmallocmem;

unsigned char *vmallocmem;

int init_module(void)

{

 pagemem = get_free_page(0);

 printk("<1>pagemem=%s",pagemem);

 kmallocmem = kmalloc(100,0);

 printk("<1>kmallocmem=%s",kmallocmem);

 vmallocmem = vmalloc(1000000);

 printk("<1>vmallocmem=%s",vmallocmem);

}

void cleanup_module(void)

{

 free_page(pagemem);

 kfree(kmallocmem);

 vfree(vmallocmem);

}

 

內存管理實例

代碼功能介紹

我們希望能通過訪問用戶空間的內存達到讀取內核數據的目的,這樣便可進行內核空間到用戶空間的大規模信息傳輸。

具體的講,我們要利用內存映射功能,將系統內核中的一部分虛擬內存映射到用戶空間,從而使得用戶空間地址等同與被映射的內核內存地址。

代碼結構體系介紹

內核空間內存分配介紹

因此我們將試圖寫一個虛擬字符設備驅動程序,通過它將系統內核空間映射到用戶空間——將內核虛擬內存映射到用戶虛擬地址。當然映射地址時少不了定位內核空間對應的物理地址,並且還要建立新的用戶頁表項,以便用戶進程尋址時能找到對應的物理內存。

從中應該看出,需要我完成既定目標,我們需要獲得:被映射內核空間物理地址  建立對應的用戶進程頁表

在內核空間中主要存在kmalloc分配的物理連續空間和vmalloc分配的非物理連續空間。kmalloc分配的空間往往被稱爲內核邏輯地址,由於它是連續分配(直接處理物理頁框),而且分配首地址一定,所以其分配的內核虛擬地址對應的實際物理地址很容易獲得:內核虛擬地址—PAGE_OFFSET0xC0000000)(內核有對應例程virt_to_phys)即等於物理地址,而且其對應的頁表屬於內核頁表(swapper_pg_dir)——在系統初始化時就以建立,因此省去了建立頁表的工作。

vmalloc分配的空間被稱爲內核虛擬地址,它的問題相對要複雜些,這是因爲其分配的內核虛擬內存空間並非直接操作頁框,而是分配的是vm_struct結構。該結構邏輯上連續但對應的物理內存並非連續,也就是說它vamlloc分配的內核空間地址所對應的物理地址並非可通過簡單線性運算獲得。從這個意義上講,它的物理地址在分配前是不確定的,因此雖然vmalloc分配的空間與kmalloc一樣都是由內核頁表來映射的,但vmalloc分配內核虛擬地址時必須更新內核頁表。

 

註釋:vmalloc分配的內核虛擬內存與kmalloc/get_free_page分配的內核邏輯內存位於不同的區間,不會重疊。因爲內核空間被分區管理,各司其職。進程空間地址分佈從0到3G(其實是到PAGE_OFFSET,0x86中它等於0xC0000000),從3Gvmalloc_start這段地址是物理內存映射區域(該區域中包含了內核鏡像、物理頁框表mem_map等等)比如我使用的系統內存是64M(可以用free看到),那麼(3G——3G+64M)這片內存就應該映射物理內存,而vmalloc_start位置應在3G+64M附近(說附近因爲是在物理內存映射區與vmalloc_start期間還回存在一個8M大小的gap來防止躍界),vmalloc_end的位置接近4G(說接近是因爲最後位置系統會保留一片128k大小的區域用於專用頁面映射,還由可能會由高端內存映射區,這些都是細節,這裏我們不做糾纏)

       另一個需要澄清的是,vmalloc分配的內核空間,其結構是vm_area,可千萬別與用戶空間malloc分配的vm_area_struct結構混淆。前者由內核頁表映射,而後者則由用戶頁表映射。

 

 

進程地址空間

 

物理內存映射區kmalloc分配

 

 Vmalloc 分配區

 

 

3Gpage_offset

 

內核虛擬空間

 

Vmalloc_start

 

Vmalloc_end

 

上圖是內存分佈的模糊輪廓

 

實例藍圖

爲了近可能豐富我們的例子程序的場景,我們選擇映射vmalloc分配的內核虛擬空間(下面我們簡稱爲vk地址)到用戶空間。

要知道用戶進程操作的是虛擬內存區域vm_area_struct,我們此刻需要將用戶vma區間利用用戶頁表映射到vk對應的物理內存上去(如下圖所示)。這裏主要工作便是建立用戶也表項完成映射工作,而這個工作完全落在了vma->nopage[3]操作上,該方法會幫助我們在發生“缺頁”時,動態構造映射所需物理內存的頁表項。

 

 

用戶虛擬空間Vm_area_struct

Vk空間vm_struct

物理內存

Vma->nopage

 

 

 

 

我們需要實現nopage方法,動態建立對應頁表,而在該方法中核心任務是尋找到vk地址對應的內核邏輯地址[4]。這必然需要我們做以下工作:

a)         找到vmalloc虛擬內存對應的內核頁表,並尋找到對應的內核頁表項。

b)        獲取內核頁表項對應的物理頁框指針。

c)        通過頁框得到對應的內核邏輯地址

基本函數

我們實例將利用一個虛擬字符驅動程序,驅動負責將一定長的內核虛擬地址(vmalloc分配的)映射到設備文件上,以便可以通過訪問文件內容來達到訪問內存的目的。這樣做的最大好處是提高了內存訪問速度,並且可以利用文件系統的接口編程(設備在Linux中作爲特殊文件處理)訪問內存,降低了開發難度。

 

 Map_driver.c就是我們的虛擬字符驅動程序,不用說它要實現文件操作表(file_operations——字符驅動程序主要做的工作便是實現該結構)中的,爲了要完成內存映射,除了常規的open/release操作外,必須自己實現mmap操作,該函數將給定的文件映射到指定的地址空間上,也就是說它將負責把vmalloc分配的內核地址映射到我們的設備文件上。

我們下面就談談mmap操作的實現細節:

文件操作的mmap操作是在用戶進行系統調用mmap[5]時被執行的,而且在調用前內核已經給用戶找到並分配了合適的虛擬內存區域vm_area_struct,這個區域將代表文件內容,所以剩下要做的便是如何把虛擬區域和物理內存掛接到一起了,即構造頁表。由於我門前面所說的原因,我們系統中頁表需要動態分配,因此不可使用remap_page_range函數一次分配完成,而必須使用虛擬內存區域自帶的nopage方法,在現場構造頁表。這樣以來,文件操作的mmap的方法只要完成“爲它得到的虛擬內存區域綁定對應的操作表vm_operations”即可。於是主要的構造工作就落在了vm_operations中的nopage方法上了。

Nopage方法中核心內容上面已經提到了是“尋找到vk地址對應的內核邏輯地址”,這個解析內核頁表的工作是需要自己編寫輔助函數vaddr_to_kaddr來完成的,它所作的工作概括來講就是上文提到的a/b/c三條。

有關整個任務執行路徑請看下圖。

STEP BY STEP

編譯map_driver.cmap_driver.o模塊,具體參數見Makefile

加載模塊 insmod map_driver.o

生成對應的設備文件

/proc/devices下找到map_driver對應的設備命和設備號:grep mapdrv /proc/devices

建立設備文件mknod  mapfile c 254 0  (在我係統裏設備號爲254

    利用maptest讀取mapfile文件,將取自內核的信息(”ok”——我們在內核中在vmalloc分配的空間中填放的信息)打印到用戶屏幕。

 

全部程序下載 mmap.tar (感謝Martin Frey,該程序主體出自他的靈感)

 

 

 

 

 

 轉自:http://www.cnblogs.com/hoys/category/295216.html 



[1] 還有些情況必須要求內存連續,比如DMA傳輸中使用的內存,由於不涉及頁機制所以必須連續分配。

[2] 這種存儲池的思想在計算機科學裏廣泛應用,比如數據庫連接池、內存訪問池等等。

[3] 構建用戶也表項,除了使用nopage一次一頁的動態構造,還又一種方法remap_page_range可以一次構造一段內存範圍的也表項,但顯然這個方法時針對物理內存連續被分配時使用的,而我們vk對應的物理內存並非連續,所以這裏使用nopage

[4] 很多人一定會問,爲什麼不直接找到物理地址那,而要找內核邏輯地址呢? 沒錯,我們本意應該是獲得物理地址,但是爲了利用內核提供的一些現成的例程,如virt_to_page等(它們都是針對內核邏輯地址而言的),我們不妨轉化成內核邏輯地址來做,別忘了內核邏輯地址與理地址僅僅相差一個偏移量。

[5] 系統調用mmap原形是void *mmap2(void *start, size_t length, int prot, int flags, int fd, off_t pgoff)。

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