|
空閑頁框 |
APP |
內(nèi)存區(qū)域 vm_area_structs |
malloc、fork、excute、mmap |
brk/do_map |
get_free_page(s) |
用戶空間 |
內(nèi)核空間 |
進(jìn)程虛擬地址空間 |
系統(tǒng)調(diào)用 |
進(jìn)程頁表 |
請頁異常 |
內(nèi)核程序 |
物理內(nèi)存影射區(qū) |
Vmalloc分配區(qū) |
slab |
get_free_page(s) |
內(nèi)核頁表 |
get_free_page(s) |
請頁異常 |
進(jìn)程地址空間 物理內(nèi)存映射區(qū) 0 內(nèi)核虛擬空間 Vmalloc_start Vmalloc_end
上圖是內(nèi)存分布的模糊輪廓
由get_free_page或Kmalloc函數(shù)所分配的連續(xù)內(nèi)存都陷于物理映射區(qū)域,所以它們返回的內(nèi)核虛擬地址和實際物理地址僅僅是相差一個偏移量(PAGE_OFFSET),你可以很方便的將其轉(zhuǎn)化為物理內(nèi)存地址,同時內(nèi)核也提供了virt_to_phys()函數(shù)將內(nèi)核虛擬空間中的物理影射區(qū)地址轉(zhuǎn)化為物理地址。要知道,物理內(nèi)存映射區(qū)中的地址與內(nèi)核頁表是有序?qū)?yīng),系統(tǒng)中的每個物理頁框都可以找到它對應(yīng)的內(nèi)核虛擬地址(在物理內(nèi)存映射區(qū)中的)。
而vmalloc分配的地址則限于vmalloc_start與vmalloc_end之間。每一塊vmalloc分配的內(nèi)核虛擬內(nèi)存都對應(yīng)一個vm_struct結(jié)構(gòu)體(可別和vm_area_struct搞混,那可是進(jìn)程虛擬內(nèi)存區(qū)域的結(jié)構(gòu)),不同的內(nèi)核虛擬地址被4k打大小空閑區(qū)的間隔,以防止越界——見下圖)。與進(jìn)程虛擬地址的特性一樣,這些虛擬地址可與物理內(nèi)存沒有簡單的位移關(guān)系,必須通過內(nèi)核頁表才可轉(zhuǎn)換為物理地址或物理頁。它們有可能尚未被映射,在發(fā)生缺頁時才真正分配物理頁框。
這里給出一個小程序幫助大家認(rèn)請上面幾種分配函數(shù)所對應(yīng)的區(qū)域。
#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);
}
我們希望能通過訪問用戶空間的內(nèi)存達(dá)到讀取內(nèi)核數(shù)據(jù)的目的,這樣便可進(jìn)行內(nèi)核空間到用戶空間的大規(guī)模信息傳輸。
具體的講,我們要利用內(nèi)存映射功能,將系統(tǒng)內(nèi)核中的一部分虛擬內(nèi)存映射到用戶空間,從而使得用戶空間地址等同與被映射的內(nèi)核內(nèi)存地址。
因此我們將試圖寫一個虛擬字符設(shè)備驅(qū)動程序,通過它將系統(tǒng)內(nèi)核空間映射到用戶空間——將內(nèi)核虛擬內(nèi)存映射到用戶虛擬地址。當(dāng)然映射地址時少不了定位內(nèi)核空間對應(yīng)的物理地址,并且還要建立新的用戶頁表項,以便用戶進(jìn)程尋址時能找到對應(yīng)的物理內(nèi)存。
從中應(yīng)該看出,需要我完成既定目標(biāo),我們需要獲得:被映射內(nèi)核空間物理地址 和 建立對應(yīng)的用戶進(jìn)程頁表。
在內(nèi)核空間中主要存在kmalloc分配的物理連續(xù)空間和vmalloc分配的非物理連續(xù)空間。kmalloc分配的空間往往被稱為內(nèi)核邏輯地址,由于它是連續(xù)分配(直接處理物理頁框),而且分配首地址一定,所以其分配的內(nèi)核虛擬地址對應(yīng)的實際物理地址很容易獲得:內(nèi)核虛擬地址—PAGE_OFFSET(0xC0000000)(內(nèi)核有對應(yīng)例程virt_to_phys)即等于物理地址,而且其對應(yīng)的頁表屬于內(nèi)核頁表(swapper_pg_dir)——在系統(tǒng)初始化時就以建立,因此省去了建立頁表的工作。
而vmalloc分配的空間被稱為內(nèi)核虛擬地址,它的問題相對要復(fù)雜些,這是因為其分配的內(nèi)核虛擬內(nèi)存空間并非直接操作頁框,而是分配的是vm_struct結(jié)構(gòu)。該結(jié)構(gòu)邏輯上連續(xù)但對應(yīng)的物理內(nèi)存并非連續(xù),也就是說它vamlloc分配的內(nèi)核空間地址所對應(yīng)的物理地址并非可通過簡單線性運算獲得。從這個意義上講,它的物理地址在分配前是不確定的,因此雖然vmalloc分配的空間與kmalloc一樣都是由內(nèi)核頁表來映射的,但vmalloc分配內(nèi)核虛擬地址時必須更新內(nèi)核頁表。
注釋:vmalloc分配的內(nèi)核虛擬內(nèi)存與kmalloc/get_free_page分配的內(nèi)核邏輯內(nèi)存位于不同的區(qū)間,不會重疊。因為內(nèi)核空間被分區(qū)管理,各司其職。進(jìn)程空間地址分布從0到
另一個需要澄清的是,vmalloc分配的內(nèi)核空間,其結(jié)構(gòu)是vm_area,可千萬別與用戶空間malloc分配的vm_area_struct結(jié)構(gòu)混淆。前者由內(nèi)核頁表映射,而后者則由用戶頁表映射。
進(jìn)程地址空間 物理內(nèi)存映射區(qū)kmalloc分配 Vmalloc 分配區(qū) 0 內(nèi)核虛擬空間 Vmalloc_start Vmalloc_end
上圖是內(nèi)存分布的模糊輪廓
為了近可能豐富我們的例子程序的場景,我們選擇映射vmalloc分配的內(nèi)核虛擬空間(下面我們簡稱為vk地址)到用戶空間。
要知道用戶進(jìn)程操作的是虛擬內(nèi)存區(qū)域vm_area_struct,我們此刻需要將用戶vma區(qū)間利用用戶頁表映射到vk對應(yīng)的物理內(nèi)存上去(如下圖所示)。這里主要工作便是建立用戶也表項完成映射工作,而這個工作完全落在了vma->nopage[3]操作上,該方法會幫助我們在發(fā)生“缺頁”時,動態(tài)構(gòu)造映射所需物理內(nèi)存的頁表項。
用戶虛擬空間Vm_area_struct |
Vk空間vm_struct |
物理內(nèi)存 |
Vma->nopage |
我們需要實現(xiàn)nopage方法,動態(tài)建立對應(yīng)頁表,而在該方法中核心任務(wù)是尋找到vk地址對應(yīng)的內(nèi)核邏輯地址[4]。這必然需要我們做以下工作:
a) 找到vmalloc虛擬內(nèi)存對應(yīng)的內(nèi)核頁表,并尋找到對應(yīng)的內(nèi)核頁表項。
b) 獲取內(nèi)核頁表項對應(yīng)的物理頁框指針。
c) 通過頁框得到對應(yīng)的內(nèi)核邏輯地址。
我們實例將利用一個虛擬字符驅(qū)動程序,驅(qū)動負(fù)責(zé)將一定長的內(nèi)核虛擬地址(vmalloc分配的)映射到設(shè)備文件上,以便可以通過訪問文件內(nèi)容來達(dá)到訪問內(nèi)存的目的。這樣做的最大好處是提高了內(nèi)存訪問速度,并且可以利用文件系統(tǒng)的接口編程(設(shè)備在Linux中作為特殊文件處理)訪問內(nèi)存,降低了開發(fā)難度。
Map_driver.c就是我們的虛擬字符驅(qū)動程序,不用說它要實現(xiàn)文件操作表(file_operations——字符驅(qū)動程序主要做的工作便是實現(xiàn)該結(jié)構(gòu))中的,為了要完成內(nèi)存映射,除了常規(guī)的open/release操作外,必須自己實現(xiàn)mmap操作,該函數(shù)將給定的文件映射到指定的地址空間上,也就是說它將負(fù)責(zé)把vmalloc分配的內(nèi)核地址映射到我們的設(shè)備文件上。
我們下面就談?wù)?/span>mmap操作的實現(xiàn)細(xì)節(jié):
文件操作的mmap操作是在用戶進(jìn)行系統(tǒng)調(diào)用mmap[5]時被執(zhí)行的,而且在調(diào)用前內(nèi)核已經(jīng)給用戶找到并分配了合適的虛擬內(nèi)存區(qū)域vm_area_struct,這個區(qū)域?qū)⒋砦募?nèi)容,所以剩下要做的便是如何把虛擬區(qū)域和物理內(nèi)存掛接到一起了,即構(gòu)造頁表。由于我門前面所說的原因,我們系統(tǒng)中頁表需要動態(tài)分配,因此不可使用remap_page_range函數(shù)一次分配完成,而必須使用虛擬內(nèi)存區(qū)域自帶的nopage方法,在現(xiàn)場構(gòu)造頁表。這樣以來,文件操作的mmap的方法只要完成“為它得到的虛擬內(nèi)存區(qū)域綁定對應(yīng)的操作表vm_operations”即可。于是主要的構(gòu)造工作就落在了vm_operations中的nopage方法上了。
Nopage方法中核心內(nèi)容上面已經(jīng)提到了是“尋找到vk地址對應(yīng)的內(nèi)核邏輯地址”,這個解析內(nèi)核頁表的工作是需要自己編寫輔助函數(shù)vaddr_to_kaddr來完成的,它所作的工作概括來講就是上文提到的a\b\c三條。
有關(guān)整個任務(wù)執(zhí)行路徑請看下圖。
編譯map_driver.c為map_driver.o模塊,具體參數(shù)見Makefile
加載模塊 :insmod map_driver.o
生成對應(yīng)的設(shè)備文件
1 在/proc/devices下找到map_driver對應(yīng)的設(shè)備命和設(shè)備號:grep mapdrv /proc/devices
2 建立設(shè)備文件mknod mapfile c 254 0 (在我系統(tǒng)里設(shè)備號為254)
利用maptest讀取mapfile文件,將取自內(nèi)核的信息(”ok”——我們在內(nèi)核中在vmalloc分配的空間中填放的信息)打印到用戶屏幕。
全部程序下載 mmap.tar (感謝Martin Frey,該程序主體出自他的靈感)
[1] 還有些情況必須要求內(nèi)存連續(xù),比如DMA傳輸中使用的內(nèi)存,由于不涉及頁機(jī)制所以必須連續(xù)分配。
[2] 這種存儲池的思想在計算機(jī)科學(xué)里廣泛應(yīng)用,比如數(shù)據(jù)庫連接池、內(nèi)存訪問池等等。
[3] 構(gòu)建用戶也表項,除了使用nopage一次一頁的動態(tài)構(gòu)造,還又一種方法remap_page_range可以一次構(gòu)造一段內(nèi)存范圍的也表項,但顯然這個方法時針對物理內(nèi)存連續(xù)被分配時使用的,而我們vk對應(yīng)的物理內(nèi)存并非連續(xù),所以這里使用nopage。
[4] 很多人一定會問,為什么不直接找到物理地址那,而要找內(nèi)核邏輯地址呢? 沒錯,我們本意應(yīng)該是獲得物理地址,但是為了利用內(nèi)核提供的一些現(xiàn)成的例程,如virt_to_page等(它們都是針對內(nèi)核邏輯地址而言的),我們不妨轉(zhuǎn)化成內(nèi)核邏輯地址來做,別忘了內(nèi)核邏輯地址與理地址僅僅相差一個偏移量。
[5] 系統(tǒng)調(diào)用mmap原形是void *mmap2(void *start, size_t length, int prot, int flags, int fd, off_t pgoff)。
|