內存管理實例
代碼功能介紹
我們希望能通過訪問用戶空間的內存達到讀取內核數據的目的,這樣便可進行內核空間到用戶空間的大規模信息傳輸。
具體的講,我們要利用內存映射功能,將系統內核中的一部分虛擬內存映射到用戶空間,從而使得用戶空間地址等同與被映射的內核內存地址。
代碼結構體系介紹
內核空間內存分配介紹
因此我們將試圖寫一個虛擬字符設備驅動程序,通過它將系統內核空間映射到用戶空間——將內核虛擬內存映射到用戶虛擬地址。當然映射地址時少不了定位內核空間對應的物理地址,並且還要建立新的用戶頁表項,以便用戶進程尋址時能找到對應的物理內存。
從中應該看出,需要我完成既定目標,我們需要獲得:被映射內核空間物理地址 和 建立對應的用戶進程頁表。
在內核空間中主要存在kmalloc分配的物理連續空間和vmalloc分配的非物理連續空間。kmalloc分配的空間往往被稱爲內核邏輯地址,由於它是連續分配(直接處理物理頁框),而且分配首地址一定,所以其分配的內核虛擬地址對應的實際物理地址很容易獲得:內核虛擬地址—PAGE_OFFSET(0xC0000000)(內核有對應例程virt_to_phys)即等於物理地址,而且其對應的頁表屬於內核頁表(swapper_pg_dir)——在系統初始化時就以建立,因此省去了建立頁表的工作。
而vmalloc分配的空間被稱爲內核虛擬地址,它的問題相對要複雜些,這是因爲其分配的內核虛擬內存空間並非直接操作頁框,而是分配的是vm_struct結構。該結構邏輯上連續但對應的物理內存並非連續,也就是說它vamlloc分配的內核空間地址所對應的物理地址並非可通過簡單線性運算獲得。從這個意義上講,它的物理地址在分配前是不確定的,因此雖然vmalloc分配的空間與kmalloc一樣都是由內核頁表來映射的,但vmalloc分配內核虛擬地址時必須更新內核頁表
註釋:vmalloc分配的內核虛擬內存與kmalloc/get_free_page分配的內核邏輯內存位於不同的區間,不會重疊。因爲內核空間被分區管理,各司其職。進程空間地址分佈從0到3G(其實是到PAGE_OFFSET,在0x86中它等於0xC0000000),從3G到vmalloc_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結構混淆。前者由內核頁表映射,而後者則由用戶頁表映射。
|
|
|
|
|
|
|
|
上圖是內存分佈的模糊輪廓
爲了近可能豐富我們的例子程序的場景,我們選擇映射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) 通過頁框得到對應的內核邏輯地址。
[3] 構建用戶也表項,除了使用nopage一次一頁的動態構造,還又一種方法remap_page_range可以一次構造一段內存範圍的也表項,但顯然這個方法時針對物理內存連續被分配時使用的,而我們vk對應的物理內存並非連續,所以這裏使用nopage。
[4] 很多人一定會問,爲什麼不直接找到物理地址那,而要找內核邏輯地址呢? 沒錯,我們本意應該是獲得物理地址,但是爲了利用內核提供的一些現成的例程,如virt_to_page等(它們都是針對內核邏輯地址而言的),我們不妨轉化成內核邏輯地址來做,別忘了內核邏輯地址與理地址僅僅相差一個偏移量。
基本函數
我們實例將利用一個虛擬字符驅動程序,驅動負責將一定長的內核虛擬地址(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三條。
有關整個任務執行路徑請看下圖。
[5] 系統調用mmap原形是void *mmap2(void *start, size_t length, int prot, int flags, int fd, off_t pgoff)。
編譯map_driver.c爲map_driver.o模塊,具體參數見Makefile
加載模塊 :insmod map_driver.o
生成對應的設備文件
1 在/proc/devices下找到map_driver對應的設備命和設備號:grep mapdrv /proc/devices
2 建立設備文件mknod mapfile c 254 0 (在我係統裏設備號爲254)
利用maptest讀取mapfile文件,將取自內核的信息(”ok”——我們在內核中在vmalloc分配的空間中填放的信息)打印到用戶屏幕